1.程序:为了完成某个指定的任务,用否种编程语言编写的一组指令的集合。即指一段静态的代码(静态对象)
2.进程:正在运行的一个程序(动态过程)
3.线程:是一个程序内部的一条执行路径
举例:电脑管家:当没点击打开之前,就是一个程序;当点击打开后,将电脑管家加载在内存中,此时相当于一个进程;电脑管家每一项(体检,杀毒,清理)都是一个线程。
二、多线程的创建与使用
1.多线程的创建
方式一:继承Thread类
package com.slxy.thread1; /* 多线程创建方式一:继承于Thread类 1.创建一个继承于Thread的子类 2.重写Thread中的run()方法 3.创建Thread子类的对象 4.通过子类对象来调Thread中的start()方法 */ public class ThreadTest extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String[] args) { ThreadTest threadTest1 = new ThreadTest(); threadTest1.start();//1.启动当前线程 2.调用当前线程的run()方法 ThreadTest threadTest2 = new ThreadTest(); threadTest2.start(); //threadTest1.run(); 错误原因:不能通过直接调用run()方法的方式来启动线程 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } 拓展://创建Thread类匿名子类的方式: new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }.strat();
图解:
方式二:实现Runnable接口
package com.slxy.thread1; /* 创建多线程的方式二:实现Runnable接口 1.创建一个类来实现Runnable接口 2.实现类去实现Runnable中的抽象方法:run() 3.创建实现类的对象 4.将此对象作为参数传递到Thread类的构造器中,创建Thread对象 5.通过Thread类的对象调用start()方法:a.启动线程 b.调用当前线程的run()-----调用了Runnable类型的target的run() */ class Test implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+""+i); } } } public class RunnableTest { public static void main(String[] args) { Test test = new Test(); Thread t1 = new Thread(test); t1.start(); Thread t2 = new Thread(test); t2.start(); } }
JDK5.0新增线程创建方式-->方式三、四
方式三:实现Callable接口
package com.slxy.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /*创建线程的第三种方式:实现Callable接口 1.创建一个类来实现Callable接口 2.实现call()方法,将此线程需要执行的操作声明在call方法中(有返回值,声明抛出异常,支持泛型操作) 3.创建实现类的对象 4.以实现类作为参数创建FutureTask对象 5.以FutureTask作为参数创建Thread对象 6.通过Thread对象调用start()方法 */ public class CallableTest implements Callable<Integer> { @Override public Integer call() throws Exception { int num=0; for(int i=0;i<=10;i++){ num += i; } return num; } public static void main(String[] args) { CallableTest ct = new CallableTest(); FutureTask ft = new FutureTask(ct); Thread t = new Thread(ft); t.start(); try { Integer i = (Integer)ft.get();//返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值 System.out.println("1-10的和:"+i); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
方式四:使用Executor框架创建线程池
使用线程池的优点:
1.提高响应速度(减少创建新线程的时间)
2.降低资源的消耗(重复利用线程池中的线程,不需要每次都创建)
3.便于线程管理(设置核心池的大小,最大线程数)
package com.slxy.thread; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /*创建线程的第四种方式:使用Executor框架创建线程池 1.提供指定线程数量的线程池 2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 3.关闭连接池 */ public class ThreadPoolTest implements Runnable{ @Override public void run() { for (int i=0;i<100;i++){ System.out.println(i); } } public static void main(String[] args) { //创建线程池 ExecutorService service = Executors.newFixedThreadPool(10); //执行操作 service.execute(new ThreadPoolTest());//适用于Runnable //service.submit(Callable callable);适用于Callable //关闭线程池 service.shutdown(); } }
2.Thread类中的常用方法:
1)启动当前线程,调用当前线程的run()方法
2)run():通常需要重写Thread中的此方法,将创建线程所要执行的操作写在里面
3)Thread.currentThread():静态方法,返回执行当前代码的线程
4)getName():获取当前线程的名字
5)setName():设置当前线程的名字
6)yield():释放当前cpu的执行权
7)join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完后A才结束阻塞状态。
8)stop():方法过时
9)sleep(long millis):让当前线程睡眠(阻塞),单位毫秒(1000ms = 1s)
10)isAlive():判断当前线程是否还存活,返回值为boolean类型
/*线程命名的两种方式: 1.通过线程对象.setName()的方式 eg:t1.setName("线程1"); 2.通过创建构造器的方式 eg:ThreadMethodTest(String name){ super(name); } */ public class ThreadMethodTest extends Thread{ ThreadMethodTest(String name){ super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); if(i % 10 == 0){ yield(); } } } public static void main(String[] args) { ThreadMethodTest t1 = new ThreadMethodTest("线程1"); // t1.setName("线程1"); t1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); if(i == 10){ try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(t1.getName()+":"+t1.isAlive()); } }
3.线程的优先级:
MAX_PRIORITY:10//最大优先级
MIN_PRIORITY:1//最小优先级
NORM_PRIORITY:5//默认优先级
如何获取和设置优先级:
getPriority();
setPriority();
注意:优先级越高只是意味着高概率的被先执行,但是不代表是优先级高的先执行完后优先级低的才执行。
4.卖票举例:
package com.slxy.thread1; //卖票:创建3个窗口,总票数为:100张 //用继承Thread类的方法 class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket); ticket--; }else { break; } } } } public class WindowTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("线程1"); w2.setName("线程2"); w3.setName("线程3"); w1.start(); w2.start(); w3.start(); } }
package com.slxy.thread1; //卖票:创建3个窗口,总票数为:100张 //用实现Runnable接口的方法 class Test1 implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket); ticket--; }else{ break; } } } } public class WindowTest1 { public static void main(String[] args) { Test1 test1 = new Test1(); Thread t1 = new Thread(test1); Thread t2 = new Thread(test1); Thread t3 = new Thread(test1); t1.start(); t2.start(); t3.start(); } }
说明:
开发中优先选择实现Runnable接口的方式
原因:1.实现的方式没有单继承性的局限性
2.实现的方式更适合来处理多线程有共享数据的情况(eg:卖票)
联系:public class Thread implements Runnable{}
相同点:两种方式都重写了run()方法,将线程要执行的逻辑声明在run()中。
5.线程的生命周期
1.新建 2.就绪 3.运行 4.阻塞 5.死亡
图解:
6.线程安全问题的产生---以卖票为例:
1.问题:买票过程中出现重票,错票问题---出现线程安全问题
2.原因:在某个线程操作车票的过程中,尚未操作完成时,其他线程也参与进来了
//会出现错票现象,也就是票数<=0的情况 //当ticket只剩1号票时,线程0 1 2 同时进入堵塞状态,当线程0先于1 2 sleep()结束时,ticket--将1号票卖了,线程1卖的就是0号票,线程2卖的就是-1号票,这时就出现错票现象。 public void run() { while(true){ if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket); ticket--; }else{ break; } } }
图解:
//会出现重票现象:当ticket=1,线程1进来进入堵塞状态,此时一号窗口卖的票号为ticket=1,这时线程2也进来进入堵塞状态,此时二号窗口卖的还是ticket=1,再ticket--,就会出现重票号现象。 while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; }else{ break; } } }
7.解决线程安全问题:在java中通过同步机制
同步方式的优点与局限性:
优点:解决了线程安全问题
局限性:效率低。因为操作同步代码时,只能有一个线程参加,也就相当于单线程的过程。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.需要被同步的代码指的是:操作共享数据的代码
2.共享数据是指:多个线程共同操作的变量,如ticket
3.同步监视器是指:俗称锁。任何一个类的对象都可以充当锁(obj)
//同步代码块处理实现Runnable接口的线程安全问题 class Test1 implements Runnable{ private int ticket = 100; Object obj = new Object(); @Override public void run() { while(true){ synchronized(obj){//synchronized(this){//写法二,this指当前类的对象 if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket); ticket--; }else{ break; } } } } } public class WindowTest1 { public static void main(String[] args) { Test1 test1 = new Test1(); Thread t1 = new Thread(test1); Thread t2 = new Thread(test1); Thread t3 = new Thread(test1); t1.start(); t2.start(); t3.start(); } }
//同步代码块处理继承Thread类线程安全问题 class Window extends Thread{ private static int ticket = 100; private static Object obj = new Object();//加static保证了共用一把锁 @Override public void run() { while(true){ synchronized (obj){//synchronized(Window.class){//方式二:Window.class只会加载一次 if(ticket > 0){ System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket); ticket--; }else { break; } } } } } public class WindowTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("线程1"); w2.setName("线程2"); w3.setName("线程3"); w1.start(); w2.start(); w3.start(); } }
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,此时可以将此方法声明为同步的。
//同步方法处理实现Runnable接口的线程安全问题 class Test2 implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ show(); } } private synchronized void show(){//同步监视器:this if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket); ticket--; } } } public class WindowTest2 { public static void main(String[] args) { Test2 test2 = new Test2(); Thread t1 = new Thread(test2); Thread t2 = new Thread(test2); Thread t3 = new Thread(test2); t1.start(); t2.start(); t3.start(); } }
//同步方法处理继承Thread类的线程安全问题 class Window3 extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ show(); } } private static synchronized void show(){//同步监视器:当前类(Window3) if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket); ticket--; } } } public class WindowTest3 { public static void main(String[] args) { Window3 w1 = new Window3(); Window3 w2 = new Window3(); Window3 w3 = new Window3(); w1.setName("线程1"); w2.setName("线程2"); w3.setName("线程3"); w1.start(); w2.start(); w3.start(); } }
使用同步机制将单例模式中的懒汉式改写为线程安全的。
class Bank{ private Bank(){} private static Bank instance = null; 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; } }
方式三:加Lock锁-----JDK5.0新增
package com.slxy.thread1; import com.slxy.thread.ThreadPoolTest; import java.util.concurrent.locks.ReentrantLock; //解决线程安全问题的另一种方法:加Lock锁 public class LockTest implements Runnable{ private int ticket = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(true);//fire默认值为false----是否公平 @Override public void run() { while(true){ try{ //2.调用锁定lock()方法 lock.lock(); if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"窗口售票,票号为:"+ticket); ticket--; }else{ break; } }finally { //3.调用解锁方法 lock.unlock(); } } } public static void main(String[] args) { LockTest lockTest = new LockTest(); Thread t1 = new Thread(lockTest); Thread t2 = new Thread(lockTest); t1.start(); t2.start(); } }
问题:
synchronized 和 Lock的异同:
相同:都可以解决线程安全问题
不同:synchronized机制在执行相应的代码块时,自动的释放同步监视器
8.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
9.线程通信
//例子:使用两个线程打印1-100,线程1和线程2交替执行 package com.slxy.thread1; public class CommunicationTest extends Thread{ private static int num = 1; @Override public void run() { while(true){ synchronized (CommunicationTest.class){ CommunicationTest.class.notify(); if(num <= 100){ System.out.println(Thread.currentThread().getName()+":"+num); num++; try { CommunicationTest.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } } } } public static void main(String[] args) { CommunicationTest test1 = new CommunicationTest(); CommunicationTest test2 = new CommunicationTest(); test1.setName("线程一"); test2.setName("线程二"); test1.start(); test2.start(); } }
/*线程通信的应用例子:生产者和消费者的问题 问题:有个工厂它的最大库存量为10,当库存量大于10时生产者等待,唤醒消费者消费;当库存量等于0时消费者等待,唤醒生产者生产。 分析:1.多线程问题:生产者线程,消费者线程 2.有线程安全问题:存在共享资源(产品) 3.同步机制解决线程安全问题 4.涉及线程通信 */ package com.slxy.thread1; //工厂 class Factory{ private int count = 0;//初始库存量为0 //生产方法 public synchronized void add(){ if(count < 10){ count++; System.out.println(Thread.currentThread().getName()+"生产第"+count+"个产品"); this.notify(); }else{ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //消费方法 public synchronized void get(){ if(count > 0){ System.out.println(Thread.currentThread().getName()+"消费第"+count+"个产品"); count--; this.notify(); }else{ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //生产者 class Producer extends Thread{ Factory factory; public Producer(Factory factory) { this.factory = factory; } @Override public void run() { while(true){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } factory.add(); } } } //消费者 class Customer extends Thread{ Factory factory; public Customer(Factory factory) { this.factory = factory; } @Override public void run() { while(true){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } factory.get(); } } } public class FactoryTest { public static void main(String[] args) { Factory factory = new Factory(); Producer producer = new Producer(factory); Customer customer = new Customer(factory); Thread t1 = new Thread(producer); Thread t2 = new Thread(customer); t1.setName("生产者"); t2.setName("消费者"); t1.start(); t2.start(); } }
10.问题:sleep()与wait()方法的区别:
相同点:一旦执行此方法,都可以使得当前线程进入阻塞状态。
不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),object类中声明wait()。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中时,sleep()不释放锁,线程虽然进入休眠,其他线程依然无法访问这个对象;wait释放锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
原文:https://www.cnblogs.com/miao-wu/p/14682997.html