一文学会多线程
Posted 进击地小白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文学会多线程相关的知识,希望对你有一定的参考价值。
线程和进程的概念
程序:是为完成特定任务,用某种语言编写的一组指令的集合。即一段静态的代码,静态对象。
进程:指在运行中的程序,程序一旦运行就是进程,同时进程也是是线程的容器,是系统进行资源分配和调度的单元,系统会在运行时为每个进程分配不同的内存取余,是资源分配的最小单元。是一个动态的过程:有它自身的产生,存在和消亡的过程。--生命周期
线程(thread)是操作系统能够进行运算调度和执行的最小单元。它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,是一个程序内部的一条执行路径,一个进程中可以并发多个线程,每条线程可以执行不同的任务。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)
多线程的创建
继承Thread类
/** * 创建多线程的方式一: * 1.继承Thread类 * 2.重写Thread类中的run()方法 * 3.创建Thread类子类的对象 * 4.通过此对象调用start() * 例子:遍历100000以内的所有偶数 */ public class ThreadTest public static void main(String[] args) MyThread m1 = new MyThread(); m1.start(); for (int i = 0; i < 100000; i++) if(i%2 == 0) System.out.println(i+"main"); class MyThread extends Thread @Override public void run() for (int i = 0; i <100000 ; i++) if(i%2 == 0) System.out.println(i);
会发现执行结果是相交的
…… 27250main 27252main 81768 81770 ……/** * 练习 * 创建俩个多线程,分别打印100以内的奇数和偶数 */ public class ThreadTest public static void main(String[] args) MyThread1 m1 = new MyThread1(); MyThread2 m2 = new MyThread2(); m1.start(); m2.start(); class MyThread1 extends Thread @Override public void run() for (int i = 0; i <100 ; i++) if(i%2 == 0) System.out.println(Thread.currentThread().getName()+": "+i); class MyThread2 extends Thread @Override public void run() for (int i = 0; i <100 ; i++) if(i%2 == 1) System.out.println(Thread.currentThread().getName()+": "+i);
运行结果
Thread-0: 0 Thread-1: 1 Thread-0: 2 Thread-1: 3 Thread-0: 4 Thread-1: 5 Thread-0: 6 Thread-1: 7 Thread-0: 8 Thread-1: 9 Thread-0: 10 Thread-1: 11 Thread-0: 12 Thread-1: 13 Thread-0: 14 Thread-1: 15 ……
实现Runnable接口
/** * 创建多线程的方式二: * 1.创建一个实现了Runnable接口的类 * 2.实现类去实现Runnable接口中的抽象方法:run() * 3.创建实现类的对象 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 * 5.通过Thread类的对象调用start() */ public class RunnableTest public static void main(String[] args) Run run=new Run(); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); thread1.start(); thread2.start(); class Run implements Runnable @Override public void run() for (int i = 0; i < 100000; i++) System.out.println(Thread.currentThread().getName()+" "+i);//Thread類源码 public class Thread implements Runnable private Runnable target; public Thread(Runnable target) this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L); public void run() if (this.target != null) this.target.run();
多线程实现窗口卖票的两种方式
public class WindowsTest public static void main(String[] args) Windows windows1 = new Windows(); Windows windows2 = new Windows(); Windows windows3 = new Windows(); windows1.setName("窗口1"); windows2.setName("窗口3"); windows3.setName("窗口2"); windows1.start(); windows2.start(); windows3.start(); class Windows extends Thread private static int ticket = 100;//注意static @Override public void run() while(true) if(ticket>0) System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; else break; public class RunnableTest public static void main(String[] args) Run run=new Run(); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); Thread thread3 = new Thread(run); thread1.start(); thread2.start(); thread3.start(); class Run implements Runnable private int ticket=100;//这里不用加static @Override public void run() while(true) if(ticket>0) System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; else break;
多线程创建两种方式的比较
开发中:优先选择实现Runnable接口的方式
-
实现的方式没有单继承的局限性
-
实现的方式更适合来处理多个线程共享数据
联系:Thread继承了Runnable接口
相同点:都需要重写run方法,将线程执行的逻辑写在run方法中
JDK5.0新增线程创建方式
实现Callable接口
-
相比Runnable,可以有返回值
-
方法可以抛出异常
-
支持泛型的返回值
-
需要借助FutureTask类,比如获取返回结果
创建线程的方式三:
-
创建一个实现callable的接口
-
实现call方法,将此线程需要执行的操作声明在call()中
-
创建callable接口实现类的对象
-
将此callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
-
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start方法
-
获取callable中call方法的返回值
public class CallableTest public static void main(String[] args) ThreadTests t = new ThreadTests(); FutureTask futureTask1 = new FutureTask(t); new Thread(futureTask1).start(); try Object sum = futureTask1.get(); System.out.println("总合"+sum); catch (Exception e) e.printStackTrace(); class ThreadTests implements Callable @Override public Object call() throws Exception int num=0; for (int i = 0; i <= 100; i++) if (i % 2 == 0) System.out.println(i); num += i; return num;
线程池创建线程
优点:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
public class ThreadPool public static void main(String[] args) ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new NumberThreadTest());//适合Runnable接口 service.execute(new NumberThreadTest()); //service.submit()适合使用和callable service.shutdown();//关闭线程池 class NumberThreadTest implements Runnable private static int ticket=100; @Override public void run() while(true) if (ticket>0) System.out.println(Thread.currentThread().getName()+":"+ticket); ticket--; else break;
线程常用方法
yield();//释放当前cpu的执行权join();//在线程a中调用线程b的join方法,线程a会陷入阻塞状态直到线程b执行完毕stop();//强制线程生命期结束,不推荐使用boolean isAlive();//判断线程是否还或者sleep(long timemilltime);//让当前线程睡眠指定的milltime毫秒,在指定的milltime毫秒时间内,当前线程是阻塞状态
以下三个方法必须在同步代码块或者同步方法中使用,并且调用者必须是同步代码块或同步方法中的同步监视器(同一把锁)否则会出现IllegalMonitorStateException异常
属于Object类中的方法
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,则唤醒优先级高的notifyAll():唤醒所有线程
线程的优先级
MAX_PRIORITY:10 MIN_PRIORITY:1 NORM_PRIORITY:5
默认优先级都为5
如何获取: 1. getPriority():获取线程的优先级 2. setPriority(int p):设置线程的优先级
并不是优先级高就一定先被CPU执行,只能从概率上讲更容易被CPU执行
线程的生命周期
-
新建
-
就绪
-
运行
-
阻塞
-
死亡
synchronized
操作共享数据的代码,即为需要被同步的代码
-
共享数据:多个线程共同操作的变量
-
同步监视器:锁
-
任何一个类的对象都可以充当锁
-
多个线程必须用同一把锁
synchronized同步代码块解决线程安全问题
Runnable同步代码块解决线程安全的问题
/** * 操作共享数据的代码,即为需要被同步的代码 * 共享数据:多个线程共同操作的变量 * 同步监视器:锁 * 任何一个类的对象都可以充当锁 * 多个线程必须用同一把锁 */ public class RunnableTest public static void main(String[] args) Run run=new Run(); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); Thread thread3 = new Thread(run); thread1.start(); thread2.start(); thread3.start(); class Run implements Runnable private int ticket=100; Object object=new Object(); @Override public void run() while(true) synchronized(object)//可以使用this充当锁,this为当前对象 if(ticket>0) try Thread.sleep(200); catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; else break;
执行结果
Thread-0卖票100 Thread-0卖票99 Thread-2卖票98 Thread-1卖票97 Thread-1卖票96 Thread-1卖票95 Thread-2卖票94 Thread-2卖票93 Thread-2卖票92 Thread-2卖票91 Thread-2卖票90 Thread-2卖票89 Thread-2卖票88 Thread-2卖票87 Thread-2卖票86 Thread-2卖票85 Thread-0卖票84 Thread-0卖票83 Thread-0卖票82 Thread-0卖票81 Thread-0卖票80 Thread-0卖票79 Thread-0卖票78 Thread-0卖票77 Thread-0卖票76 Thread-0卖票75 Thread-0卖票74 Thread-0卖票73 Thread-0卖票72 Thread-0卖票71 Thread-0卖票70 Thread-0卖票69 Thread-0卖票68 Thread-0卖票67 Thread-0卖票66 Thread-0卖票65 Thread-0卖票64 Thread-0卖票63 Thread-0卖票62 Thread-0卖票61 Thread-0卖票60 Thread-2卖票59 Thread-2卖票58 Thread-2卖票57 Thread-2卖票56 Thread-2卖票55 Thread-2卖票54 Thread-2卖票53 Thread-2卖票52 Thread-2卖票51 Thread-2卖票50 Thread-2卖票49 Thread-2卖票48 Thread-2卖票47 Thread-2卖票46 Thread-2卖票45 Thread-2卖票44 Thread-2卖票43 Thread-1卖票42 Thread-1卖票41 Thread-1卖票40 Thread-1卖票39 Thread-1卖票38 Thread-1卖票37 Thread-1卖票36 Thread-1卖票35 Thread-1卖票34 Thread-1卖票33 Thread-1卖票32 Thread-1卖票31 Thread-1卖票30 Thread-1卖票29 Thread-1卖票28 Thread-1卖票27 Thread-1卖票26 Thread-2卖票25 Thread-2卖票24 Thread-2卖票23 Thread-2卖票22 Thread-2卖票21 Thread-2卖票20 Thread-2卖票19 Thread-2卖票18 Thread-2卖票17 Thread-2卖票16 Thread-2卖票15 Thread-2卖票14 Thread-2卖票13 Thread-2卖票12 Thread-2卖票11 Thread-2卖票10 Thread-2卖票9 Thread-2卖票8 Thread-2卖票7 Thread-2卖票6 Thread-2卖票5 Thread-2卖票4 Thread-2卖票3 Thread-0卖票2 Thread-0卖票1 Process finished with exit code 0
同步的方式解决了线程安全的问题,操作同步代码时,只有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
Thread方式同步代码块解决线程安全的问题
public class ThreadTest public static void main(String[] args) MyThread m1 = new MyThread(); MyThread m2 = new MyThread(); MyThread m3 = new MyThread(); m1.start(); m2.start(); m3.start(); class MyThread extends Thread private static int ticket=100; Object object=new Object(); @Override public void run() while(true) synchronized(object)//不可以使用this充当锁,这里this代表了m1,m2,m3三个对象 try sleep(10); catch (InterruptedException e) e.printStackTrace(); if(ticket > 0) System.out.println(Thread.currentThread().getName()+"卖票:"+ticket); ticket--; else break;
执行结果
Thread-0卖票:100 Thread-1卖票:100 Thread-2卖票:100 Thread-2卖票:97 Thread-1卖票:97 Thread-0卖票:97 Thread-2卖票:94 Thread-0卖票:94 Thread-1卖票:94 Thread-1卖票:91 Thread-2卖票:91 Thread-0卖票:90 Thread-2卖票:88 Thread-1卖票:88 Thread-0卖票:88 Thread-2卖票:85 Thread-0卖票:84 Thread-1卖票:84 Thread-2卖票:82 Thread-0卖票:82 Thread-1卖票:82 Thread-2卖票:79 Thread-0卖票:79 Thread-1卖票:78 Thread-2卖票:76 Thread-1卖票:76 Thread-0卖票:76 Thread-2卖票:73 Thread-1卖票:73 Thread-0卖票:73 Thread-0卖票:70 Thread-1卖票:70 Thread-2卖票:70 Thread-1卖票:67 Thread-0卖票:67 Thread-2卖票:66 Thread-2卖票:64 Thread-1卖票:64 Thread-0卖票:62 Thread-2卖票:61 Thread-1卖票:61 Thread-0卖票:61 Thread-1卖票:58 Thread-0卖票:58 Thread-2卖票:56 Thread-0卖票:55 Thread-1卖票:55 Thread-2卖票:55 Thread-0卖票:52 Thread-1卖票:52 Thread-2卖票:52 Thread-1卖票:49 Thread-2卖票:49 Thread-0卖票:49 Thread-2卖票:46 Thread-0卖票:46 Thread-1卖票:46 Thread-2卖票:43 Thread-0卖票:43 Thread-1卖票:43 Thread-2卖票:40 Thread-1卖票:40 Thread-0卖票:40 Thread-0卖票:37 Thread-2卖票:37 Thread-1卖票:37 Thread-2卖票:34 Thread-1卖票:34 Thread-0卖票:34 Thread-2卖票:31 Thread-0卖票:31 Thread-1卖票:31 Thread-1卖票:28 Thread-2卖票:28 Thread-0卖票:28 Thread-0卖票:25 Thread-2卖票:25 Thread-1卖票:25 Thread-1卖票:22 Thread-0卖票:22 Thread-2卖票:22 Thread-2卖票:19 Thread-0卖票:19 Thread-1卖票:19 Thread-2卖票:16 Thread-1卖票:16 Thread-0卖票:16 Thread-2卖票:13 Thread-0卖票:13 Thread-1卖票:13 Thread-2卖票:10 Thread-0卖票:10 Thread-1卖票:10 Thread-2卖票:7 Thread-0卖票:7 Thread-1卖票:7 Thread-2卖票:4 Thread-1卖票:4 Thread-0卖票:4 Thread-1卖票:1 Thread-2卖票:1 Thread-0卖票:1 Process finished with exit code 0
会发现还是线程不安全,这是因为三个对象使用的锁不是同一把锁了
一定要注意给object 加上static 才行
static Object object=new Object();
加入后就会发现线程又安全了
或者使用 MyThread.class 充当锁
Synchronized同步方法解决线程安全问题
使用同步方法解决线程安全问题
实现Runnable接口方式
public class SynchronizedMethod public static void main(String[] args) ThreadMethod threadMethod = new ThreadMethod(); Thread thread1 = new Thread(threadMethod); Thread thread2 = new Thread(threadMethod); Thread thread3 = new Thread(threadMethod); thread1.start(); thread2.start(); thread3.start(); class ThreadMethod implements Runnable private int ticket=100; public synchronized void show()//这里用的锁是this try Thread.sleep(10); catch (InterruptedException e) e.printStackTrace(); if(ticket > 0) System.out.println(Thread.currentThread().getName()+"卖票:"+ticket); ticket--; @Override public void run() while(ticket>0) show();
继承Thread类方式
public class SynchronizedMethodThread public static void main(String[] args) ThreadMethod threadMethod1 = new ThreadMethod(); ThreadMethod threadMethod2 = new ThreadMethod(); ThreadMethod threadMethod3 = new ThreadMethod(); threadMethod1.start(); threadMethod2.start(); threadMethod3.start(); class ThreadMethod extends Thread private static int ticket=100; public static synchronized void show()//这里必须加上static,不加上static锁使用的为this对象,这里创建了三个对象。加上static锁使用的为ThreadMethod.class try Thread.sleep(10); catch (InterruptedException e) e.printStackTrace(); if(ticket > 0) System.out.println(Thread.currentThread().getName()+"卖票:"+ticket); ticket--; @Override public void run() while(ticket>0) show();
同步方法总结:
-
同步方法仍涉及到同步监视器,只是不需要我们显示的声明
-
非静态的同步方法,同步监视器(锁)是:this
-
静态的同步方法,同步监视器(锁)是:当前类本身
锁方式解决线程安全问题
public class ReentrantLockTest public static void main(String[] args) Window window = new Window(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.start(); thread2.start(); thread3.start(); class Window implements Runnable private int ticket=100; private ReentrantLock reentrantLock=new ReentrantLock(); @Override public void run() while (true) reentrantLock.lock();//加锁 try if(ticket>0) try Thread.sleep(50); catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName()+"卖票"+ticket); ticket--; else break; finally reentrantLock.unlock();//释放锁注意:如果用继承Thread类的方式去使用lock
必须在 锁前面加上static
private static ReentrantLock reentrantLock=new ReentrantLock();
Synchronized和锁(Lock)方式的异同?
-
相同:两者都可以解决线程安全问题
-
不同:Synchronized机制在执行完相应的同步代码后,自动释放同步监视器,Lock需要手动启动同步(Lock()),同时结束也需要手动结束同步(unlock())
单例模式线程安全
class Bank private static Bank instance = null; private Bank() public static Bank getInstance() // synchronized(Bank.class)//方式一:效率稍差 // if (instance == null) // instance=new Bank(); // // return instance; // //方式二:效率更高 if (instance == null) synchronized(Bank.class) if (instance == null) instance=new Bank(); return instance;死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
死锁案例
public class DeadLock public static void main(String[] args) StringBuffer s1=new StringBuffer(); StringBuffer s2=new StringBuffer(); new Thread() @Override public void run() synchronized (s1) s1.append("a"); s2.append("1"); try Thread.sleep(100); catch (InterruptedException e) e.printStackTrace(); synchronized (s2) s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); .start(); new Thread(new Runnable() @Override public void run() synchronized (s2) s1.append("c"); s2.append("3"); try Thread.sleep(100); catch (InterruptedException e) e.printStackTrace(); synchronized (s1) s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); ).start();
多线程通信列题
public class CommunicationTest /** * 实现两个线程交替打印1-100 */ public static void main(String[] args) Test test = new Test(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); Thread t3 = new Thread(test); t1.start(); t2.start(); t3.start(); class Test implements Runnable private int number=1; @Override public void run() while (true)//while条件不能用number <= 100 因为这样就没有全锁住操作共享数据的代码,线程就不安全了 synchronized (this) //也不能锁住while 锁住了就只有一个线程能进来操作了 if (number <= 100) try Thread.sleep(20); catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName()+":"+number); number++; else break;
package com.atguigu.thread; public class CommunicationTest /** * 线程通信的例子:实现两个线程交替打印1-100 * 涉及到的方法: * wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器 * notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,则唤醒优先级高的 * notifyAll():唤醒所有线程 */ public static void main(String[] args) Test test = new Test(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); Thread t3 = new Thread(test); t1.start(); t2.start(); t3.start(); class Test implements Runnable private int number = 1; @Override public void run() synchronized (this) while (true) // while条件不能用number <= 100 // 因为这样就没有全锁住操作共享数据的代码,线程就不安全了 // 也不能锁住while 锁住了就只有一个线程能进来操作了 //除非使用以下方法来操作线程 //先阻塞当前线程,然后再次循环时唤醒被阻塞的线程 this.notify();//唤醒线程,这个this必须与sychronized锁住的对象相同 if (number <= 100) try Thread.sleep(20); catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName() + ":" + number); number++; try wait();//当前线程阻塞,并释放同步监视器(锁) catch (InterruptedException e) e.printStackTrace(); else break;
sleep()和wait()方法的异同
-
相同点:一旦执行,都会让当前线程进入阻塞状态
-
不同点:
-
声明的位置不同,sleep声明在Thread类中,wait声明在Object类中。
-
是否释放同步监视器,如果俩个方法都在同步代码块或同步方法中:sleep()不会释放锁,wait()会释放锁
-
调用的要求不同:sleep可以在任何需要的场景下调用,wait必须在同步代码块或同步方法中使用。
-
线程通信的应用:经典例题: 生产者消费者问题
/** * 线程通信的应用:经典例题: 生产者消费者问题 */ public class ProduceConsumerTest public static void main(String[] args) Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); Consumer c1 = new Consumer(clerk); p1.start(); c1.start(); class Clerk private int product = 0; public void produce() while(true) synchronized (this) this.notify(); if(product < 20) try Thread.sleep(10); catch (InterruptedException e) e.printStackTrace(); product++; System.out.println(Thread.currentThread().getName()+"生产者生产产品"+product); else try wait(); catch (InterruptedException e) e.printStackTrace(); public void consume() while (true) synchronized (this) this.notify(); if (product > 0) try Thread.sleep(10); catch (InterruptedException e) e.printStackTrace(); product--; System.out.println(Thread.currentThread().getName() + "消费者消费产品" + product); this.notify(); else try wait(); catch (InterruptedException e) e.printStackTrace(); class Producer extends Thread private Clerk clerk; public Producer(Clerk clerk) this.clerk=clerk; @Override public void run() clerk.produce(); class Consumer extends Thread private Clerk clerk; public Consumer(Clerk clerk) this.clerk=clerk; @Override public void run() clerk.consume();欢迎加入学习交流群:166543371
以上是关于一文学会多线程的主要内容,如果未能解决你的问题,请参考以下文章
Linux shell 多线程开发以及模板使用,详细一文透彻
Linux shell 多线程开发以及模板使用,详细一文透彻