JAVA学习笔记(9)多线程

线程和进程的区别:
https://blog.csdn.net/liutao43/article/details/114673657

Java实现线程的方法

Java支持多线程,并且实现也比较容易,并且Java将线程抽象为了类,并且在jvm中,一个线程代表一个独立的栈空间。

继承Thread方法,实现多线程

这一种方法主要是注意区别start和run方法的区别

public class Main {
    
    
    public static void main(String[] args){
    
    
        MyThread myThread = new MyThread();
        /*
         * 启动线程
         * start()方法作用:启动一个分支线程,在jvm中开辟一个新的栈空间,代码任务完成,瞬间就结束了,线程就启动了
         * start()方法执行成功之后,会自动调用run()方法,并且在分支栈底部压栈
         * run方法在分支栈的栈底部,main方法在主线程栈底部.run()和main()是平级的
         * run()方法不会启动线程,不能并发执行
         * start()会重新开辟出一块内存空间
         */
        myThread.start();
        for(int i = 0 ; i < 1000 ; i++) {
    
    
            System.out.println("我是main线程" + i);
        }
    }

}

/*
 * 实现线程直接编写一个类继承java.lang.Thread,必须重写run方法
 */
class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for(int i  = 0 ; i < 1000 ; i++) {
    
    
            System.out.println("我是Thread线程" +i);
        }
    }
}

在这里插入图片描述

实现Runnable接口中的run方法

实现接口Runnable

import java.util.Scanner;
/*
 * 实现线程:编写一个类,用Runnable接口,实现run()方法,然后封装到Thread中
 */
public class Main {
    
    
	public static void main(String[] args){
    
    
		// MyRunable myr = new MyRunable();
		// Thread myThread = new Thread(myr);
		
		/*
		 * 启动线程,开辟一个新分支栈空间
		 * 会自动调用实现类中的run方法
		 */
		// myThread.start();
		
		/*
		 * 采用匿名内部类实现线程
		 */
		
		Thread myThread = new Thread(new Runnable() {
    
    
			@Override
			public void run() {
    
    
				for(int i  = 0 ; i < 1000 ; i++) {
    
    
					if(i == 500) {
    
    
					Scanner s= new Scanner(System.in);
					String string = s.nextLine();
					System.out.println(string);
					}
					System.out.println("分支线程 --->" + i);
				}
			}
		});
		myThread.start();
	
		for(int i = 0 ; i < 1000 ; i++) {
    
    
			
			System.out.println("主线程--->" + i);
		}	
	}
}

/*
 * 实现Runnable接口,实现run()方法
 * 建议使用使用接口这种方式,面向抽象编程,并且可以继承其它的类
 */
class MyRunable implements Runnable{
    
    
	@Override
	public void run() {
    
    
		for(int i  = 0 ; i < 1000 ; i++) {
    
    
			System.out.println("分支线程 --->" + i);
		}
	}
}

在这里插入图片描述

线程状态

线程和进程的状态是类似的:
在这里插入图片描述

设置和获取当前线程的名称

获取当前线程:默认的分支线程名称是Thread-0,Thread-1,Thread-2…

/*
 * 获取线程名字: 线程对象:getName()
 * 获取线程对象:static void currentThread() 
 * 修改线程对象的名字: 线程对象.setName()
 */

public class Main {
    
    
	public static void main(String[] args){
    
    
		
		/*
		 * 获取线程对象:static void currentThread() 
		 */
		Thread currentThread = Thread.currentThread();		// 出现在main方法中,当前线程就是主线程
		
		MyThread myThread = new MyThread();
		myThread.setName("t1");
		System.out.println(myThread.getName());
		myThread.start();
		for(int i = 0 ; i < 1000 ; i++) {
    
    
			System.out.println( currentThread .getName() + "--->"+ i);
		}
	}

}

/*
 * 实现Runnable接口,实现run()方法
 * 建议使用使用接口这种方式,面向抽象编程,并且可以继承其它的类
 */
class MyThread extends Thread{
    
    
	@Override
	public void run() {
    
    
		for(int i  = 0 ; i < 1000 ; i++) {
    
    
			Thread currentThread = Thread.currentThread();		// 谁启动就是谁的线程对象
			System.out.println(currentThread.getName()+"--->" + i);
		}
	}
}

线程阻塞

线程阻塞的原因有很多,I/O中断等等,Java人提供了可以让线程进入阻塞的API,sleep,是Thread的静态方法。

当前线程进入阻塞状态:

/*
 * 线程的阻塞状态,使用static void sleep(long millis) ,到达时间之后回到就绪状态
 * 使当前线程进入阻塞状态
 * 进入阻塞状态之后,放弃cpu时间片,让给其他线程使用
 * Thread.sleep(long millis),隔离一段时间之后,去执行一段特定的代码
 */

public class Main {
    
    
	public static void main(String[] args){
    
    
		/*
		try {
			Thread.sleep(1000 * 5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("hello world");
		
		*/
		
		for(int i = 0 ; i < 10 ; ++i) {
    
    
			System.out.println(Thread.currentThread().getName() + "--->" + i);
			try {
    
    
				Thread.sleep(1000);
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	}
}

笔试题

Thread变量调用sleep:

静态方法使用对象调用会直接转换成类调用。

/*
 * 线程的阻塞状态,使用static void sleep(long millis) ,到达时间之后回到就绪状态
 * 使当前线程进入阻塞状态
 * 进入阻塞状态之后,放弃cpu时间片,让给其他线程使用
 * Thread.sleep(long millis),隔离一段时间之后,去执行一段特定的代码
 */


public class Main {
    
    
	public static void main(String[] args){
    
    
		
		Thread t = new MyThread();
		t.setName("t");
		t.start();
		
		/*try {
    
    
			t.sleep(1000 * 5);		// 对象调用当前线程的会转换为Thread.sleep()
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}*/
		
		System.out.println("hello world");
		
	}

}

/*
 * 实现Runnable接口,实现run()方法
 * 建议使用使用接口这种方式,面向抽象编程,并且可以继承其它的类
 */
class MyThread extends Thread{
    
    
	@Override
	public void run() {
    
    
		for(int i  = 0 ; i < 1000 ; i++) {
    
    
			Thread currentThread = Thread.currentThread();		// 谁启动就是谁的线程对象
			System.out.println(currentThread.getName()+"--->" + i);
		}
	}
}

唤醒线程

叫醒线程:

/*
 * 线程的阻塞状态,使用static void sleep(long millis),如果阻塞时间太长,可以叫醒一个睡眠的线程
 * 中断线程的睡眠
 */

public class Main {
    
    
    public static void main(String[] args){
    
    
        Thread t = new MyThread();
        t.setName("t1");
        t.start();

        try {
    
    
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        for (int i = 0 ; i < 10000 ; i++){
    
    
            t.interrupt();		// 会让线程t中的sleep抛出异常,中断这个线程,达到叫醒线程的目的
        }
    }
}

/*
 * 实现Runnable接口,实现run()方法
 * 建议使用使用接口这种方式,面向抽象编程,并且可以继承其它的类
 */
class MyThread extends Thread{
    
    

    /*
     * 子类重写的方法不能比父类的方法多抛出异常
     * 父类都没有这种异常,子类却有了,不符合逻辑
     */
	/*
	@Override
	public void run() throws InterruptedException{
		for(int i  = 0 ; i < 1000 ; i++) {
			System.out.println(Thread.currentThread.getName()+"--->" + begin);
			Thread.sleep(1000 * 60 * 60 * 24 * 365);
			System.out.println(Thread.currentThread.getName()+"--->" + end);
		}
	}*/

    @Override
    public void run(){
    
    
        for(int i  = 0 ; i < 100  ; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"--->" + "begin");
            try {
    
    
                Thread.sleep(1000 * 60 * 60 * 24 * 360);		// 如果在别线程中调用interrupt() ,会让线程此线程中的sleep抛出异常,然后会抓取异常
            }catch(InterruptedException e) {
    
    
                // e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--->" + "end");
        }
    }
}

结束进程

结束进程:

使用stop方法就相当于kill掉线程,容易造成数据丢失。

*
 * 强行终止线程stop()不建议,已经过时
 * 打一个bool标记,通过标记判断
 */
public class Main {
    
    
	public static void main(String[] args){
    
    	
		MyRunnable r = new MyRunnable();
		Thread myThread = new Thread(r);
		myThread.setName("t");
		myThread.start();
		try {
    
    
			Thread.sleep(5000);
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}
		/*
		 * 强行结束,数据丢失
		 * 过时
		 */
		// myThread.stop();
		r.run = false;
		
		
	}
}

/*
 * 在实现Runnable中打一个bool标记
 */
class MyRunnable implements Runnable{
    
    
	public boolean run = true;
	@Override
	public void run() {
    
    
		if(run) {
    
    
			for(int i =0 ; i < 10 ; i++) {
    
    
				System.out.println(Thread.currentThread().getName() + "--->" + i );
				System.out.println(run);
				if(run == false) {
    
    
					break;
				}
				try {
    
    
					Thread.sleep(1000);
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}		
			}
		}
	}
}

线程让位和合并

当前线程让位 Thread.yield()方法
当前线程阻塞,t1线程加入:t1.join().

线程的调度

一般java程序是采用抢占式调度

java的抢占调度模式:

/*
 * java采用的是抢占式调度模型,谁的优先级高,抢到的时间片就高一些/多一些
 * 利用方法来设置优先级
 * int getPriority()  和  void setPriority(int newPriority)
 * 最高优先级10
 * 最低优先级1
 * 默认优先级5
 * 优先级比较高是抢占cpu时间片概率多一些
 *
 * static void yield()  让位方法,暂停当前线程,回去继续抢占cpu时间片,并不是阻塞,而是从运行状态回到就绪状态,效果不明显
 *
 * void join()   t.join(),当前线程阻塞,t线程执行,直到t线程结束,当前线程才可以开始
 */
public class Main {
    
    
    public static void main(String[] args){
    
    
        System.out.println("最高优先级" + Thread.MAX_PRIORITY);       // 1
        System.out.println("最高优先级" + Thread.NORM_PRIORITY);     // 5
        System.out.println("最高优先级" + Thread.MIN_PRIORITY);      // 10
        System.out.println(Thread.currentThread().getName() + "线程优先级:" +Thread.currentThread().getPriority());

        MyRunnable r = new MyRunnable();
        Thread myThread = new Thread(r);
        /*
         * 优先级别越高,抢占的时间片的概率就多一点
         */
        myThread.setPriority(10);
        myThread.setName("t");
        myThread.start();

        try {
    
    
            myThread.join();		// myThreaad开始执行,当前线程结束,直到myThead结束
        } catch (InterruptedException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("main over");
    }
}

/*
 * 在实现Runnable中打一个bool标记
 */
class MyRunnable implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for(int i =0 ; i < 1000 ; i++) {
    
    
            // Thread.yield();		// 暂停此时的线程返回可执行状态
            System.out.println(Thread.currentThread().getName() + "--->" + i );
        }
    }
}

t.join方法的结果:将t线程合并到当前线程,当前线程阻塞,直到t线程结束。
在这里插入图片描述

线程同步和互斥

同步和互斥在这篇文章https://blog.csdn.net/liutao43/article/details/114673657

解决互斥的操作一般是给一段代码上锁或者使用PV操作,Java程序解决互斥和同步使用synchronzied关键字来解决互斥和同步。

解决互斥和同步的关键就是找到关键操作,这步操作必须在多线程下,可能会影响最后结果的操作。

synchronized (共享的对象){
    
    
	关键操作;
}

共享的对象就是多个线程共同使用的对象即可,这里有一个对象锁机制,每一个对象只有一个一个锁,碰见 synchronized (共享的对象)相当于获取锁,别的线程只有等花括号语句执行完毕才能再次获取该对象的锁,否则只能等待或者阻塞,其实这就是OS中的上锁操作,只有这段代码执行完了,才能释放锁。

例如,t1,t2两个线程,t1执行到synchronized (共享的对象)这里,就代表共享的对象的锁被站,t2执行到synchronized (共享的对象)无法获取对象锁,所以就只能等待,这样就解决了线程互斥。

/*
 * 线程安全问题
 *
 * 模拟两个线程对一个账户进行取款,出现线程安全问题
 */
public class Main {
    
    
    public static void main(String[] args){
    
    
        Account act = new Account("act-001" , 10000 );
        AccountThread t1 = new AccountThread(act);
        AccountThread t2 = new AccountThread(act);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class Account {
    
    
    private String account;
    private double balance;
    public Account() {
    
    }
    public Account(String account, double balance) {
    
    
        this.account = account;
        this.balance = balance;
    }


    public String getAccount() {
    
    
        return account;
    }

    public void setAccount(String account) {
    
    
        this.account = account;
    }

    public double getBalance() {
    
    
        return balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }

    public void withDraw(double money) {
    
    
            double before = this.getBalance();
            double after = before - money;
            /*
             * t1执行到这里,但还没来得及执行setBalance这行代码,t2线程进来withdraw方法并执行了withDraw方法中的double before = this.getBalance();,此时一定出问题
             * t1线程执行完毕,不会出问题
             */
         
         	 this.setBalance(after);
    }
}

class AccountThread extends Thread{
    
    
    private Account act;

    public AccountThread(Account act) {
    
    
        this.act = act;
    }

    @Override
    public void run() {
    
    
        double money = 5000;
        act.withDraw(money);
        System.out.println(Thread.currentThread().getName() + "账户" + act.getAccount() + " 取款成功 , 余额:" + act.getBalance());
    }
}

有可能出现两个余额都为5000的现状,就代表获余额并扣钱的时候,这时候变成另一个线程运行,也是获取相应额钱数。

解决互斥

/*
 * 线程安全问题
 *
 * 模拟两个线程对一个账户进行取款,出现线程安全问题
 */
public class Main {
    
    
    public static void main(String[] args){
    
    
        Account act = new Account("act-001" , 10000 );
        AccountThread t1 = new AccountThread(act);
        AccountThread t2 = new AccountThread(act);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class Account {
    
    
    private String account;
    private double balance;
    public Account() {
    
    }
    public Account(String account, double balance) {
    
    
        this.account = account;
        this.balance = balance;
    }


    public String getAccount() {
    
    
        return account;
    }

    public void setAccount(String account) {
    
    
        this.account = account;
    }

    public double getBalance() {
    
    
        return balance;
    }

    public void setBalance(double balance) {
    
    
        this.balance = balance;
    }

    public void withDraw(double money) {
    
    
            /*
             * 上锁操作
             */
        synchronized (this){
    
    
                double before = this.getBalance();
                double after = before - money;
                this.setBalance(after);
            }   // 执行完就释放锁
    }
}

class AccountThread extends Thread{
    
    
    private Account act;

    public AccountThread(Account act) {
    
    
        this.act = act;
    }

    @Override
    public void run() {
    
    

        double money = 5000;
        act.withDraw(money);
        System.out.println(Thread.currentThread().getName() + "账户" + act.getAccount() + " 取款成功 , 余额:" + act.getBalance());
    }
}

解决方法还可以扩大化,直接在线程类中加synchronized关键字

synchronized (act){
    
    
	act.withDraw(money);
}   // 执行完就释放锁

解决方法还可以在方法加,指定this为唯一参数,获取自己对象的锁,把整个方法上锁

public synchronized void withDraw(double money) {
    
    
        /*
         * 上锁操作
         */
        double before = this.getBalance();
        double after = before - money;
        this.setBalance(after);
    }

类锁:这个个上面没关系,类锁只有一种。对象锁相当于有多少各对象就有多少个锁。

public synchronized static void withDraw(double money) {
    
    
        /*
         * 上锁操作
         */
        double before = this.getBalance();
        double after = before - money;
        this.setBalance(after);
    }

死锁

死锁:当多进程或者多线程因为硬件资源不足,或者是线程运行永远也等不到继续的条件,这个时候程序就会卡住,一直等下去。

死锁代码:

/*
 * 死锁代码要会写
 * 
 * t1抢占了o1的锁,开始睡眠,这时候t2抢占了o2的锁,这时候o1和o2都被占用,就不能被继续抢占了,所以两端程序就永远死锁
 */
public class Main {
    
    
    public static void main(String[] args)throws Exception{
    
    
        Object o1 = new Object();
        Object o2 = new Object();

        Thread t1 = new MyThread1(o1 , o2);
        Thread t2 = new MyThread2(o1 , o2);
        t1.setName("t1");
        t2.setName("t2");
		
        t1.start();
        t2.start();
    }
}


class MyThread1 extends Thread{
    
    
    private final Object o1;
    private final Object o2;

    public MyThread1(Object o1 , Object o2) {
    
    
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
    
    
        synchronized (o1) {
    
    
            try {
    
    
                System.out.println(Thread.currentThread().getName() + ": o1 lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o2) {
    
    
                System.out.println(Thread.currentThread().getName() + ": o2 lock");
            }
        }
    }
}

class MyThread2 extends Thread{
    
    
    private final Object o1;
    private final Object o2;

    public MyThread2(Object o1 , Object o2) {
    
    
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
    
    
        synchronized (o2) {
    
    
            System.out.println(Thread.currentThread().getName() + ": o2 lock");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (o1) {
    
    
                System.out.println(Thread.currentThread().getName() + ": o1 lock");
            }
        }
    }
}

守护线程

Java语言中的线程有用户线程和守护线程(GC)。

所有的用户线程结束,守护线程自动结束。主线程是一个用户线程。

实现守护线程

视同线程的实例方法setDaemon(true)设置为true

守护线程和定时器:

import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.TimerTask;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new StoreDataThread();
        t1.setName("t1");
        t1.setDaemon(true);
        t1.start();

        for (int i = 0 ; i < 10 ; i++){
    
    
            try{
    
    
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "---->" + i);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }

    }
}

class StoreDataThread extends Thread{
    
    
    public void run() {
    
    
        int i = 0;
        while(true) {
    
    
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try{
    
    
                Thread.sleep(1000);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
    }
}

class LogTimerTask extends TimerTask{
    
    
    @Override
    public void run() {
    
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":成功完成一次数据备份");
    }
}

实现线程的第三种方式

实现线程的第三种方式.Callable接口,得到返回值,run方法没有返回值。

实现Callable接口,并重写call方法。将其对象传给FutureTask的构造方法。

import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
/*
 * 实现线程的第三种方式:实现Callable接口
 * 这种方式实现的线程可以获取线程的返回值
 *
 */
public class Main {
    
    
    public static void main(String[] args){
    
    
        // 创建一个未来任务类
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
    
    
            public Integer call() throws Exception{
    
         // call方法相当于run方法。
                System.out.println("call method begin");
                Thread.sleep(1000);
                System.out.println("call method end!");
                Integer a = 100;
                return a;
            }
        });
        Thread t = new Thread(task);
        t.start();
        /*
         * 这是在main的线程中,在主线程中取得t线程的结果.
         * get()会导致当前主线程阻塞,必须等待t线程执行完,直到获取最后的结果
         */
        Integer i;
        try {
    
    
            i = task.get();
            System.out.println(i);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("hello world");
    }
}

生产者消费者问题

import javax.swing.*;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.ArrayList;
/*
 * 使用wait方法和notify方法实现生产者消费者模式
 * 生产线程负责生产 , 消费线程负责消费
 * 生产线程和消费线程要达到均衡
 *
 * wait和notify方法不是线程对象的方法,是普通java对象都有的方法
 * wait方法和notify方法建立在线程同步的基础之上.因为线程同时操作一个仓库.有线程安全问题
 * wait方法作用:o.wait()是让正在o对象正在执行的当前线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
 * notify方法的作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁
 *
 *
 * 模拟:
 * 仓库用List集合
 * 1个代表仓库满了,0表示仓库空了
 */
public class Main {
    
    
    public static void main(String[] args){
    
    
        List list = new ArrayList();

        Thread t1 = new Thread(new Prodeucer(list));
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}

class Prodeucer implements Runnable{
    
    
    private List list;
    public Prodeucer(List list) {
    
    
        this.list = list;
    }
    @Override
    public void run() {
    
    
        int i = 0;
        while(i <= 10) {
    
    
            synchronized (list) {
    
    
                if(list.size() > 0 ) {
    
    		// 仓库中有元素
                    try {
    
    
                        // 满了,需要等待,并且释放掉Producer之前占有的list锁
                        list.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                // 到这里残酷肯定是空,要进行生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName()+ "--->" + obj);
                // 唤醒消费者进行消费
                list.notifyAll();
            }
            i++;
        }
    }
}

class Consumer implements Runnable{
    
    
    private List list;
    public Consumer(List list) {
    
    
        this.list = list;
    }
    @Override
    public void run() {
    
    
        int i = 0;
        while(i <= 10) {
    
    
            synchronized (list) {
    
    
                if(list.size()== 0) {
    
    
                    try {
    
    
                        list.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName()+ "--->" + obj);
                // 唤醒生产者生产
                list.notifyAll();
                // list.notifyAll();
            }
            i++;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/liutao43/article/details/114647364