-
引入多线程(参考疯狂java讲义):
线程的引入:单线程的程序往往功能非常有限,例如我们需要开发一个简单的服务器程度,这个服务器程序需要向不同的客户端提供服务时,不同客户端之间应该互不干扰,负责会让客户端感觉沮丧,单线程的程序只有一个顺序执行流,多线程的程序可以包括多个顺序执行流,多个顺序流之间互不干扰。
几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己打堆栈,自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便但我们需要保证线程不会妨碍同一进程里的其他线程。
当线程被cpu执行时cpu开始工作,线程需要和软件或者硬件进行交互时,cpu处于空闲状态,引入多线程可以提高cpu的使用效率。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但我们可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是相互独立的。
简而言之:一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。
进程:计算机执行的任务
线程:执行进程中的每个小任务,多线程
计算机在执行过程中,在同一时间只能让CPU的核执行一个进程,进程有多个线程来执行,在同一时刻cpu只能让一个核工作,在同一时刻cpu只能处理一个线程。在实际应用中,多线程是非常有用的,一个浏览器必须能同时下载多个图片:一个Web服务器必须能同时响应多个用户请求;java虚拟机本身就在后台提供了一个超级线程来进行垃圾回收;图形用户界面也需要启动单独的线程来从主机环境手机用户界面时间-----总之,度线程在实际编程中的应用非常广泛。
-
线程不安全三要素:
多线程、线程间共有资源、有写操作,只要破坏其中之一就能达到线程安全
破坏多线程:通过加锁机制、
破坏公有资源:ThreadLocal、
破坏写操作:只由一个线程完成写操作或者不进行写操作
-
创建多线程的方式
java使用Thread类代表线程,所有的线程对象必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际就是执行一段程序流(一段顺序执行的代码)。java使用run方法来封装这样一段程序流。
public class ThreadDemo4 extends Thread{
private int i;
@Override
public void run() {
for ( ; i <100 ; i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if (i==20){
new ThreadDemo4().start();
new ThreadDemo4().start();
}
}
}
}
console
....
main 63
main 64
main 65
main 66
Thread-0 0
...
Thread-0 99
main 67
....
main 98
main 99
Thread-1 0
...
Thread-1 99
注意:进行多线程编程时不要忘记了java程序运行时默认的主线程,main方法的方法体就是主线程的执行体。
Thread.currentThread():currentThread是Thread类的静态方法,该方法总是返回当前整在执行的线程对象。
getName();该方法是Thread的实例方法,该方法返回调用该方法的线程的名字
setName();程序可以通过setName(String name)方法为线程设置名字,也可以通过getName()方法返回指定线程的名字。在默认的情况下,主线程的名字为mian,用户启动的多条线程的名字依次为Thread-0,Thread-1。。。。。。
1、继承Thread类,重写run方法(线程代码逻辑所在位置),调用start方法,用于开启线程。
public class ThreadDemo1 {
public static void main(String[] args) {
//让线程执行 --- 执行线程代码逻辑所在的类
Demo d = new Demo();
//开始线程 --- Thread类里的方法
d.start();
//主逻辑代码
for (int i = 10 ; i >0 ; i--){
System.out.println("main:"+i);
}
}
}
//线程任务执行的代码逻辑
class Demo extends Thread{
//线程的代码逻辑
@Override
public void run(){
for (int i = 0 ; i <= 10 ; i++){
System.out.println("i:"+i);
}
}
}
2、实现Runnable接口,重写run方法(线程代码逻辑),通过Runnable接口的实现类对象构建Thread类对象,调用start方法开启线程,可以在new Thread的参数中加上创建线程的名字
public class ThreadDemo2 {
public static void main(String[] args) {
//通过Runnable实现类对象构建Thread类对象
Thread t = new Thread(new TDemo());
t.start();
for (int i = 10; i >0 ; i--) {
System.out.println("main"+i);
}
}
}
//线程代码逻辑所在类---实现Runnable接口
class TDemo implements Runnable{
//重写run方法---线程代码逻辑
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("sub"+i);
}
}
}
提示: Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run方法,出于这种考虑,Thread类是否可以把任意对象的任意方法作为线程执行体?Java语言是不可以的,Java语言的Thread必须使用Runnable对象的run方法作为线程执行体;但C#可以把任何对象的任意方法来作为线程执行体。
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if (i==20) {
new Thread(new SecondThread(), "线程1").start();
new Thread(new SecondThread(), "线程2").start();
}
}
}
}
采用Runnable接口的方式来创建的多条的线程可以共享线程类的实例属性。这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多条线程可以共享同一个target,所以多条线程可以共享一个线程类(实际上应该是线程的target类)的实例属性。
通过集成Thread或实现Runable接口都可以实现多线程,但两种方式存在一定的差别,相比之下两种方式的主要差别如下:
实现Runnable接口方式的多线程:
优势:线程类只是实现了Runnable接口,还可以集成其他类。
在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从 而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势 :编程稍稍复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。
继承Thread类方式的多线程:
优势:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获取当前线程
劣势:因为线程类已经继承了Thread类,所以不能再继续继承其他父类
3、实现Callable接口,重写call方法
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程类对象
DTDemo1 dt =new DTDemo1();
//通过获取执行服务器,帮助创建线程
ExecutorService es =Executors.newCachedThreadPool();
//
Future<Integer> u=es.submit(dt);
//
System.out.println(u.get());
}
}
class DTDemo1 implements Callable<Integer>{
//重写方法----线程代码逻辑所在的地方
@Override
public Integer call() throws Exception {
return 20;
}
}
-
线程的生命周期
在线程的生命周期中它要进过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态。因为线程启动之后它不能一直霸占CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
1、新建和就绪状态
程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征名称须也不会执行线程的线程执行体。
当线程对象调用了start()方法后,该线程就处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,这个状态线程并没有开始运行,该线程何时开始运行,取决于JVM里线程调度器的调度。
注意:不要对已经处于启动状态的线程再次调用start方法,否则将引发IllegalThreadStateException异常。
提示:如果程序希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒------一毫秒足矣,因为在这一毫秒内CPU不会空闲,他就会去执行另一条就绪状态的线程,这样就可以让我们的子线程立即获得执行。
2、运行和阻塞状态
如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程处于运行状态。
抢占式策略系统:所有现代的桌面和服务器操作系统都是采用抢占式调度策略,即系统会给每个可执行的线程一个小时间段来处理任务,当该事件段用完系统就会剥夺该线程所占据的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。
协作式调度策略:一些小型设备如手机则可能采用协作式调度,在这样的系统中,只有当一个线程调用了它的sleep或yield方法后才会放弃所占用的资源,也就是必须由该线程主动放弃所占用的资源。
当发生如下情况下,线程将进入阻塞状态:
①线程调用sleep方法主动放弃所占用的处理器资源。
②线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
③线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
④线程在等待某个通知(notify)
⑤程序调用了线程的suspend方法将该线程挂起。不过这个方法容易导致死锁,所以程序应该避免使用该方法。
当前正在执行的线程被阻塞之后,等他有合适的机会会重新进入就绪状态,也就是说阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度他。
当发生如下特定的情况将可以解除上面的阻塞,让线程重新进入就绪状态
①调用sleep方法的线程经过了指定时间。
②线程调用的阻塞式IO方法已经返回。
③线程成功地获取了试图取得同步监视器。
④线程正在等待某个通知时,其他线程发出了一个通知。
⑤处于挂起状态的线程调用了resume恢复方法。
3、线程死亡
线程会以以下三种方式之一结束,结束后就处于死亡状态:
① run()方法执行完成,线程正常结束。
②线程抛出一个未捕获的Exception或Error
③直接调用该线程的stop()方法来结束该线程-------该方法容易导致死锁,通常不推荐使用
提示:想要测试某条线程是否已经死亡,可以调用线程对象的isAlive()方法,当线程处于就绪、运行和阻塞三种状态时,该方法就返回true,当线程处于新建、死亡两种状态时,该方法将返回false。
不要试图对一个已经死亡的线程调用start()方法使他重新启动,死亡就是死亡,该线程将不可再次作为线程执行,如果试图用start启动该线程会引发IllegalThreadStateException异常,程序只能对新建状态的线程调用start方法,对新建状态的线程两次调用start方法也是错误的
注意:当主线程结束的时候,其他线程不受任何影响,并不会随之结束,一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受主线程影响。
4、控制线程
Thread提供了一个线程等待另一个线程完成的方法:join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join方法加入的jion线程执行完成为止。
public class JoinThread extends Thread{
//提供有参数的构造器,设置该线程的名字
public JoinThread(String name){super(name);}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动子线程
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++) {
if (i==20){
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
//main线程调用了jt线程的join方法,
//main线程必须等jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
上面程序中一共有三条线程,主方法开始时就启动了名为“新线程”的子线程,该子线程将会和main线程并发执行。当主线程的循环变量i等于20时,启动了名为“被join的线程”的线程,该线程不会和main线程并发执行,而是main线程必须等该线程执行结束后才可以向下执行。在名为“被join的线程”的线程执行时实际只有两条子线程并发执行,而主线程处于等待状态。
join方法有几种重载的形式:
join():等待被join的线程执行完成。
join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内,被join的线程还没有结束则不再等待
5、后台线程
因为底层多线程之间存在抢占问题,抢占发生在代码的每一步,导致了数据安全问题。
加锁------同步代码块锁、同步方法锁(每次只能走一个线程)
同步代码锁:synchronized(锁对象){}------锁对象指的是可以被线程共享:方法区里的内容可以被所有线程共享(加锁的实质:对多少个线程对象进行加锁,这些线程对象都是同步的)
同步方法锁:在方法上加上synchronized,如果非静态方法锁的对象是this 如果是静态方法锁的是当前类名.class
售票案例。总共销售100张票,四个售票员销售
public class SellTicketText1 {
public static void main(String[] args) {
//创建票类对象
Ticket t = new Ticket();
//设置票数
t.setCount(100);
//四个售票员
Seller s1 = new Seller(t);
Seller s2 = new Seller(t);
Seller s3 = new Seller(t);
Seller s4 = new Seller(t);
//开启线程
new Thread(s1,"A").start();
new Thread(s2,"B").start();
new Thread(s3,"C").start();
new Thread(s4,"D").start();
/* 想用this来锁,
this指代当前类对象每个对象单独加锁---一个对象被四个线程执行
new Thread(s1,"A").start();
new Thread(s1,"B").start();
new Thread(s1,"C").start();
new Thread(s1,"D").start();*/
}
}
//模仿卖票---线程的代码逻辑
class Seller implements Runnable{
//引入票类
Ticket t;
//有参构造
public Seller(Ticket t){
this.t=t;
}
//线程的代码逻辑---卖票的过程
//同步方法锁:如果非静态方法锁的对象是this 如果是静态方法锁的是当前类名.class
@Override
public synchronized void run() {
//卖票
while (true){
//同步代码块锁(锁对象)
// synchronized (t){//Seller.class Math.class
if(t.getCount()<=0){break;}
//票卖结束就是票数为0
//设置新的票数
t.setCount(t.getCount()-1);
//打印具体哪个售票员----具体是哪个线程执行
//Thread.currentThread()----当前正在执行的线程
System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩多少张"+t.getCount()+"票。。");
// }
}
}
}
//表示票的类
class Ticket{
//属性-----票数
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
锁之间的相互嵌套---死锁
通过锁的等待唤醒机制来解决死锁
public class DeadLockDemo {
//
static print p = new print();
static scan s = new scan();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//打印信息
synchronized (p){
p.print();
//让线程进行休眠,释放执行权
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
synchronized (s){
s.scan();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s){
s.scan();
synchronized (p){
p.print();
}
}
}
}).start();
}
}
class print{
public void print(){
System.out.println("在打印。。。。");
}
}
class scan{
public void scan(){
System.out.println("在扫描。。。。。");
}
}
wait和sleep区别
sleep----如果线程没有加锁就会释放线程的执行权,如果加锁就不会释放执行权,可以指定休眠时间,Thread类的静态方法
wait-----可以指定等待时间,时间到了也能唤醒,如果不指定时间就只能手动唤醒,不管有没有锁都会释放线程的执行权Object里的普通方法
public class WaitNotifyDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("lili");
//开启线程
new Thread(new Ask(s)).start();
new Thread(new Change(s)).start();
}
}
//线程所在的类
class Ask implements Runnable{
private Student s;
public Ask(Student s){
this.s=s;
}
@Override
public void run() {
while (true){
synchronized (s){
//释放线程执行权----等待
if(s.flag==false)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出
System.out.println("老师你好,我是"+s.getName()+"我是一个"+s.getGender()+"想问问题...");
s.notify();
//改变标志位
s.flag=false;
}
}
}
}
//线程所在的类---换学生
class Change implements Runnable{
private Student s;
public Change(Student s){
this.s=s;
}
@Override
public void run() {
while(true){
synchronized (s){
//
if(s.flag==true)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (s.getName().equals("Tom")){
s.setName("jack");
s.setGender('M');
}else {
s.setName("Tom");
s.setGender('F');
}
//线程唤醒
s.notify();
//改变标志位
s.flag=true;
}
}
}
}
class Student{
private String name;
private char gender;
boolean flag=true;
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
守护线程
被守护线程一旦执行结束守护线程也就结束了,只要不是守护线程就是被守护线程-----GC(垃圾回收的GC就是守护线程)
public class DemoDemo {
public static void main(String[] args) {
//创建出小兵对象
Thread t1 = new Thread(new Soilder(),"小兵1");
Thread t2 = new Thread(new Soilder(),"小兵2");
Thread t3 = new Thread(new Soilder(),"小兵3");
Thread t4 = new Thread(new Soilder(),"小兵4");
//设置守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
//被守护线程
for (int i = 10; i >=0; i--) {
System.out.println("boss剩余"+i);
}
}
}
//线程类---小兵
class Soilder implements Runnable{
@Override
public void run() {
//输出每个小兵的剩余血量
for (int i = 10; i >=0 ; i--) {
System.out.println(Thread.currentThread().getName()+"还剩"+i+"滴血");
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程优先级
级别分为(1-10),理论上优先级越大越有概率抢到执行权,如果线程之间的优先级差值超过了5才会有一点点差别
public class PririotyDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new PDemo(),"A");
Thread t2 = new Thread(new PDemo(),"B");
//设置优先级
t1.setPriority(1);
t2.setPriority(9);
//开启线程
t1.start();
t2.start();
}
}
class PDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
同步:在某一时刻一个线程执行结束才能轮到下一个线程。
异步:在某一时刻一个正在执行的线程可能被抢占执行
同步一定是安全的,安全的不一定是同步,不安全一定是异步,异步不一定不安全。
宏观上上述描述没有问题,在微观上同步是安全的,异步是不安全的。
线程经典练习:生产消费模型
public class WaitNotifyText1 {
//生产消费问题
public static void main(String[] args) {
Product p = new Product();
new Thread(new Productor(p),"生产者").start();
new Thread(new Productor(p),"生产者").start();
new Thread(new Consumer(p),"消费者").start();
new Thread(new Consumer(p),"消费者").start();
}
}
//模拟生产过程---线程逻辑代码
class Productor implements Runnable{
//引入商品类
Product p;
public Productor(Product p){
this.p=p;
}
@Override
public void run() {
synchronized (p) {
while(true) {
while (p.flag==false){//加上while保证线程一定会进行判断
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//此时生产的最大值
int max = 1000 - p.getNumber();//1000减去上次剩余的量
//随机生产的商品数量
int count = (int) (Math.random() * (max + 1));
//设置新的商品数量
p.setNumber(p.getNumber() + count);
//输出
System.out.println("此次生产了" + count + "个商品" + ",还剩余" + p.getNumber() + "个商品");
p.notify();//随机唤醒一个
p.flag = false;
}
}
}
}
//模拟消费过程
class Consumer implements Runnable{
//引入商品类
Product p;
public Consumer(Product p){
this.p=p;
}
@Override
public void run() {
while (true) {
synchronized (p) { //消费最大值
while (p.flag==true)
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
int max = p.getNumber();
//此次消费的随机商品数
int count = (int) (Math.random() * (max + 1));
//设置新的商品输咯昂
p.setNumber(p.getNumber() - count);
//输出
System.out.println("本次消费了" + count + "个商品,还剩余" + p.getNumber() + "个商品");
p.notify();
p.flag=true;
}
}
}
}
//商品类
class Product{
private int number;
boolean flag=true;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}