为什么要使用多线程?
使用多线程的主要优点:
第一点:是提高CPU的利用率,比如程序需要执行一些等待的任务(用户读写文件等),这是就 凸显多线程的意义了,极大的提到了线程的利用率。
第二点:是提高应用程序的响应,尤其对图形化界面更有意义,可以提高用户的体验。
第三点:改变图形结构对长的线程复杂的线程分给多个线程,独立运行,有利于理解和修改。
怎样创建多线程:
创建多线程主要有以下几种方式:
第一种方式: 继承Thred类:
- 某一个类继承Thread类。
- 在子类中实现Thread类中的run()方法。我们线程的处理逻辑也是编辑在run方法中的。
- 在主启动类中进行创建实现类的对象并且调用start()方法,用于启动这个线程。
实例:
public class shiyan1444 extends Thread{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(i);
}
}
}
class mainTa{
public static void main(String[] args) {
shiyan1444 shiyan1444 = new shiyan1444();
shiyan1444.start();
}
}
需要注意的点就是:每次创建一个线程就要重新实例化一个Thread类子类的对象,也就是相当于一个对象就是一个线程。
第二种方式:实现Runnable接口
操作步骤:
- 实现类进行实现Runnable接口。
- 然后实现接口中的run()方法。在方法内写入线程的逻辑代码。
- 在启动类上面进行创建接口实现类的对象,并且实例化Thread类的对象,参数传入实现类的对象。
- 通过Thread对象来进行调用start()方法。
实例:
public class shiyan1444 implements Runnable{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(i);
}
}
}
class mainTha{
public static void main(String[] args) {
shiyan1444 shiyan1444 = new shiyan1444();
Thread thread = new Thread(shiyan1444);
thread.start();
}
}
需要注意的点就是:只需要创建一个Runnable实现类的对象即可,只需要创建Thread对象据可以实现多线程的创建
第三种方式:创建线程池
提前创建好多个线程。提前放入线程之中,使用时直接获取 ,使用完返回池中,可以避免频繁的创建和销毁、实现重复利用。线程池的创建多线程是开发中最长用的方式。因为使用线程池有很多优点:提高响应速度、降低资源的消耗、便于线程的管理等。
public static void main(String[] args) {
//提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//进行的强制转换 将ExecutorService 接口类型转换成他的实现类ThreadPoolExecutor 类型
ThreadPoolExecutor service= (ThreadPoolExecutor) executorService;
service.setCorePoolSize();//设置线程池的属性
System.out.println(executorService.getClass());//获取线程池的属性
executorService.execute(new NumberT());//适合实现Runnadble接口时使用 提交
executorService.submit(new Numberliz());//适合使用Callable使用 执行
executorService.shutdown(); //关闭链接池
}
线程中常用的方法:
在介绍线程安全和线程通信之前需要先介绍几个常用的方法:
- setName()和getName()方法分别是进行设置线程名称和获取线程名称。
- ThreadcurrentThread()是用来获取当前线程。
- sllep()方法是让当前线程进行休眠 参数输入具体的休眠时间。
- join()方法 举例:a线程调用了b线程的join()方法,那么a线程就会进入阻塞状态,直到b线程执行完毕。.
- yield() 方法,这个方法表示在对于一个正在被cpu处理的线程而讲 执行这个方法之后会进行释放cpu的资源,让其他的线程进行使用cpu() 不是以后cpu就不处理这个方法了 下一个时刻或许cpu就会再次执行这个这个线程。
线程安全问题:
典型的生产者消费者问题,如果不进行处理线程安全问题的话,会出现 重复消费等等的问题。出现重复消费问题的原因是多个线程共享数据。
解决思路:当一个线程操作共享数据的时候,其他的线程不能参与进来,当线程a操作完其他线程才可以继续操作 ,就算线程a 是处于阻塞状态其他线程是也要进行等待 。 所以我们使用同步机制(或者是说安全锁、同步监视器)来进行处理。
那想要利用同步监视器来解决问题就要先知道什么是同步代码 同步代码块就是 共享数据: 多个线程共同操作的变量,比如 操作共享数据的代码,视为被同步的代码。
实例:
public class MyThread extends Thread {
private static Integer match=100;
@Override
public void run() {
while (true){
synchronized(MyThread.class) {// 其中的锁对象要保持唯一性
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (match > 0) {
match--;
System.out.println(Thread.currentThread().getName() + "剩余:" + match);
} else {
break;
}
}
}
}
}
class sjdk{
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.setName("窗口二");
myThread1.start();
MyThread myThread = new MyThread();
myThread.setName("窗口一:");
myThread.start();
}
需要注意的点就是在synchronized(MyThread.class)同步锁中的锁对象在多个线程的调用中必须保持唯一。
举例: 使用synchronized(this)是不可以的
上述的例子是实现 Thread类的方式来创建的多线程,这样的话就需要我们在启动类中创建多个Thread类子类的对象来实现多线程的问题。既然是创建多个对象使用synchronized(this)当然是不可以的。
还需要特别注意的是 共享资源要设置成静态的,如果不设置静态的话,因为两个线程对象并不相同所以还是会出现重复消费的问题。
线程通讯;
以上述的生产者和消费者为例 ,即使解决了线程安全的问题,但是还是会出现某个线程出现大量消费,其它线程没有机会得到消费机会的问题。为了让线程之间轮流实现消费我们使用notify()方法和wait()方法来进行解决。
是进行唤醒线程操作 例如 :线程a调用这个方法 那么会释放其他线程的阻塞状态【只会唤醒一个线程,如果有多个线程的话只会唤醒优先级高的线程】
notifyAll(), 和上一方法的区别是 它会唤醒所有的线程
wait() 方法 会进行将执行这个方法的线程进行阻塞,并释放同步监视器
实例:
public class CommunicationTest extends Thread {
private static Integer id=100;
@Override
public void run() {
while (true) {
synchronized (CommunicationTest.class) {
CommunicationTest.class.notify();
if (id > 0) {
--id;
System.out.println(Thread.currentThread().getName() + id);
try {
CommunicationTest.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
class mainT{
public static void main(String[] args) {
CommunicationTest communicationTest = new CommunicationTest();
communicationTest.setName("窗口一:");
communicationTest.start();
CommunicationTest communicationTest1 = new CommunicationTest();
communicationTest1.setName("窗口二:");
communicationTest1.start();
}
}
需要注意的点是:
notify()和wait()方法只能作用在同步代码块或者是同步方法中,并且需要使用同步监视器中的锁对象来调用。