Java线程

1. Java线程

1.1. 概念

进程:系统进行资源分配的基本单位

线程:系统进行调度和独立运行的基本单位

多线程:多个类并行交替运行

1.2. Thread类

线程的实现:继承Thread类

线程的运行:重写run()方法

线程的调用:调用线程对象的start()方法

public class TestThread extends Thread{
    @Override
    public void run(){//重写run()方法
        for(i=1;i<=20;i++){
            System.out,println("我在学习线程---" + i);
        }
    }
    
    public static void main(String[] args){
        TestThread testThread = new TestThread();//实例化TestThread类
        testThread.start();//调用线程
        
        for(i=1;i<=1000;i++){
			System.out.println("我在学习代码---" + i);
        }
    }
}

1.3. 线程之网图下载

  1. 导入commons-io jar包,创建方法downLoader,将网图下载到本地并命名

    class WebDownLoader{
        public void downLoader(String url,String name){
            try{
                FileUtils.copyURLToFile(new URL(url),new File(name));
            }catch(IOException e){
                e.printStackTrace();
                System.out.println("WebDownLoader发生异常");
            }
        }
    }
    
  2. 继承Thread类,重写run方法,主类进行实例化测试

    public class TestThread1{
        String url;
        String name;
        @Override
        public void run(){
            WebDownLoader webDownLoader = new WebDownLoader();
            webDownLoader.downloader(url,name);
            System.out.println("url:" + url + "name:" name);
        }
        
        
        public TestThread1(String url,String name){
            this.name = name;
            this.url = url;
        }
        
        public static void main(String[] args){
            TestThread1 testThread1 = new TestThread1("https://img.alicdn.com/imgextra/i1/1777871844/O1CN01vNaaq11PUawgzFNoM_!!1777871844.jpg","file1.jpg");
            TestThread1 testThread2 = new TestThread1("https://img.alicdn.com/imgextra/i3/1777871844/O1CN01FJksz31PUawZQajgk_!!1777871844.jpg","file2.jpg");
            TestThread1 testThread3 = new TestThread1("https://img.alicdn.com/imgextra/i4/1777871844/O1CN01Mx4AwZ1PUawhp4e19_!!1777871844.jpg","file3.jpg");
    		
            testThread1.start();
            testThread2.start();
    		testThread3.start();
        }
    }
    
    /*运行结果:
    url:https://img.alicdn.com/imgextra/i4/1777871844/O1CN01Mx4AwZ1PUawhp4e19_!!1777871844.jpg   name:file3.jpg
    url:https://img.alicdn.com/imgextra/i1/1777871844/O1CN01vNaaq11PUawgzFNoM_!!1777871844.jpg   name:file1.jpg
    url:https://img.alicdn.com/imgextra/i3/1777871844/O1CN01FJksz31PUawZQajgk_!!1777871844.jpg   name:file2.jpg
    */
    

    该项目目录下新增file1.jpg、file2.jpg、file3.jpg三个文件

    image-20210326212754459

1.4. Runnable接口

Runnable接口也可用于创建线程,启动线程时需使用new Thread(Runnable).start(),因为Thread类也继承了Runnable接口

public class TestRunnable implements Runnable{
    @Override
    public void run(){
        
    }
    
    public static void main(String[] args){
        TestRunnable testrunnable = new TestRunnable();
        new Thread(testRunnable).start();
    }
}

推荐使用Runnable接口,避免单继承局限性,复用线程,提高灵活性,实现代码复用。

public class GetTicket implements Runnable{
    int ticketNum = 10;
    @Override
    public void  run(){
        while(true){
            try{
            	Thread.sleep(200);
            }catch{
                e.printStackTrace();
            }
            if(ticketNum<=1){
                break;
            }
                System.out.println(Thread.currentThread().getName() + "拿了第" + ticketNum-- + "张票");//currentThread().getName()可以获取当前线程名字
        }
    }
    
    public static void main(String[] args){
    	GetTicket getTicket = new GetTicket();
        new Thread(getTicket,"黄牛一号").start();
		new Thread(getTicket,"黄牛二号").start();
        new Thread(getTicket,"黄牛三号").start();
    }
}

1.5. 使用线程模拟龟兔赛跑

public class Race implements Runnable{
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if(Thread.currentThread().getName()=="兔子"&&i%10==0){//模拟兔子睡觉情况
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag = gameOver(i);
            if(flag){//若flag为真,终止线程
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }

    private boolean gameOver(int steps){
        if(winner!=null){//当一个线程完成时终止未完成的线程
            return true;
        }{
            if(steps>=100){//当其中一个线程的步数超过100时终止线程并设置为winner
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

1.6. Callable接口

类似Runnable接口,也可以用于创建线程,有返回值,返回值类型为<>中的泛型。

如:

public class TestCallable implements Callable<Boolean> {
    String url;
    String name;
    @Override
    public Boolean call() throws Exception {
        WebDownLoder webDownLoder = new WebDownLoder();
        webDownLoder.downLoader(url,name);
        System.out.println("url:" + url + "   name:" + name);
        return true;
    }

    public TestCallable(String url,String name){
        this.name = name;
        this.url = url;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable testCallable1 = new TestCallable("https://img.alicdn.com/imgextra/i1/1777871844/O1CN01vNaaq11PUawgzFNoM_!!1777871844.jpg","file1.jpg");
        TestCallable testCallable2 = new TestCallable("https://img.alicdn.com/imgextra/i3/1777871844/O1CN01FJksz31PUawZQajgk_!!1777871844.jpg","file2.jpg");
        TestCallable testCallable3 = new TestCallable("https://img.alicdn.com/imgextra/i4/1777871844/O1CN01Mx4AwZ1PUawhp4e19_!!1777871844.jpg","file3.jpg");
		//创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
		//提交执行
        Future<Boolean> r1 = ser.submit(testCallable1);
        Future<Boolean> r2 = ser.submit(testCallable2);
        Future<Boolean> r3 = ser.submit(testCallable3);
		//获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
		//关闭服务
        ser.shutdownNow();
    }
}
//创建执行Callable的第二种方法
public TestCallable{
    public static void main(String[] args){
        FutureTask<Integer> futureTask = new FutureTask<Integer>();
        new Thread(futureTask).start();
        try{
            Integer i = futureTask.get();
            System.out.println(i);
        }catch(InterruptedException e){
            e.printStackTrace();
        }catch(ExecutionException e){
            e.printStackTrace();
        }
    }
}

class TestCallable implements Callable<Integer>{
    @Override
    public Integer call throws Exception{
        System.out.println("Callable");
        return 1;
    }
}

/*运行结果:
Callable
1
*/

1.7. 静态代理

Thread类继承了Runnable方法,并新建了start方法,Thread类可以接收Runnable对象并执行start方法,这种方式称为静态代理。

如:

public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }
}

interface Marry{//类比Runnable接口
    void HappyMarry();
}

class You implements Marry{//类比自己创建的Runnable接口对象

    @Override
    public void HappyMarry() {
        System.out.println("stone要结婚了,超开心");
    }
}

class WeddingCompany implements Marry{//类比Thread类

    private Marry target;

    public WeddingCompany(Marry target){//接收Runnable接口对象,就可以通过WeddingCompany类执行You类的HappyMarry方法
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//执行target类的HappyMarry,target和WeddingCompany都是Marry接口对象,因为这种通过其他接口对象执行自己类中的方法的方式称为静态代理
        after();
    }

    private void before() {
        System.out.println("结婚之前,布置现场");
    }
    
    private void after() {
        System.out.println("结婚之后,收尾款");
    }
    
}

1.8. lambda

lambda表达式能够在不实例化的情况下实现接口的方法,简化开发,提高开发效率。

lambda表达式作用的接口对象必须是函数式接口(即该接口中只能有一个抽象方法),属于函数式编程。

public class TestLambda{
    public static class Test2 implements Test{
        @Override
        void run(){
            System.out.println("静态内部类实现接口方法");
        }
    }

    
    public static void main(String[] args){
        Test test = new Test();
    	test = new Test1();
    	test.run();
        
        test = new Test2();
    	test.run();
        
        class Test3 implements Test{
            @Override
            void run(){
                System.out.println("局部内部类实现接口方法");
            }
        }
        test = new Test3();
        test.run();
        
        test = new Test(){//无类名,用父类或接口进行实例化
            @Override
            void run(){
                System.out.println("匿名内部类实现接口方法");
            }
        };
        test.run();
        
        test = ()->{System.out.println("lambda表达式实现接口方法")};
        test.run();
    
    }
}

interface Test{
    void run();
}

public class Test1 implements Test{
    @Override
    void run(){
        System.out.println("普通方式实现接口方法");
    }
}

1.9. 线程的五大状态

  • 创建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

1.10. 线程的基本方法

停止线程不推荐使用stop、interrupt等方法,推荐设置状态或者标识符让线程自己停下来。

Thread.sleep():线程休眠;

Thread.yield():线程礼让,不一定礼让成功,要看cpu的调度情况;

Thread.join():线程插队,插队后优先完成该线程的任务再继续执行其他线程;

Thread.getState():获取线程状态;

Thread.setPriority():设置线程的优先级;

Thread.getPriority():获取线程的优先级;

Thread.setDaemon():设置守护线程,java虚拟机无需等待守护线程执行完毕,其他非守护线程执行完毕后自动关闭守护线程。

1.11. 线程同步

线程是不安全的,当临界资源未加锁时能够同时被线程读取到线程的本地内存,这时进行的数据修改是不一致的,会出现资源的重复使用(类似于数据库事务的脏读)。如:

public class UnsafeBank {
    public static void main(String[] args) {
        Account 我的账户 = new Account(100, "我的账户");
        new Withdrawal(我的账户,50,"我").start();
        new Withdrawal(我的账户,100,"老婆").start();

    }
}


class Account{
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class Withdrawal extends Thread{
    Account account;
    int nowMoney;
    int withdrawalMoney;


    public Withdrawal(Account account,int withdrawalMoney,String name){
        super(name);
        this.account = account;
        this.withdrawalMoney = withdrawalMoney;
    }

    @Override
    public void run() {
            if(account.money-withdrawalMoney<0){
                System.out.println("钱不够,取不了");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            nowMoney = nowMoney + withdrawalMoney;
            account.money = account.money - withdrawalMoney;
            System.out.println(Thread.currentThread().getName()+"取了"+withdrawalMoney);
            System.out.println(Thread.currentThread().getName()+"手里有"+nowMoney);
        	System.out.println("账户里还有"+account.money);
    }
}

/*输入结果为
老婆取了100
我取了50
我手里有50
账户里还有-50
老婆手里有100
账户里还有-50
*/

synchronized

synchronized是一个关键字,可以修饰方法或代码块,用来给修改的部分上锁,如:

public synchronized void run(){
}


synchronized(){
    //括号内放要锁的对象
}

当有多个实例对象,无法通过锁该对象的类中的方法,因为锁的默认作用对象是this,也就是类的实例对象,当出现这种情况时,我们可以用锁代码块的方法,将这些实例对象的类的class放到括号内,实现了类的上锁,这样就可以在多实例对象的情况下实现线程同步。如上述银行取款的例子中可以将account作为锁的对象:

@Override
public void run() {
    synchronized(account){
        if(account.money-withdrawalMoney<0){
            System.out.println("钱不够,取不了");
            return;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        nowMoney = nowMoney + withdrawalMoney;
        account.money = account.money - withdrawalMoney;

        System.out.println(Thread.currentThread().getName()+"取了"+withdrawalMoney);
        System.out.println(Thread.currentThread().getName()+"手里有"+nowMoney);
        System.out.println("账户里还有"+account.money);
    }
}

1.12. 死锁

死锁:一组线程中每个线程都在等待其他线程释放资源,则这组线程是死锁的。

当临界资源的数量只有1时,可以用static 类型名 名称 = new 类名表示。

使用synchronized关键字后过一段时间会自动释放锁。

1.13. Lock锁

Lock锁与synchronized类似,都能够对线程进行加锁,通过ReentrantLock类进行实现。

private final ReentrantLock reentrantLock = new ReentrantLock();

与synchronized不同的是,Lock锁是显示进行地上锁和解锁。

reentrantLock.lock();//进行上锁
reentrantLock.unlock();//进行解锁

Lock只能锁代码块,不能锁方法。如下,便对try{}中的代码块进行加锁。

try{
    lock.lock();
    if(ticketNums>0){
        System.out.println(Thread.currentThread().getName()+"拿了第"+ticketNums-- +"张票");
    }else{
        break;
    }
}finally {
    lock.unlock();
}

1.14. 管程法和信号灯法

管程法:利用缓存区进行线程同步;(类似于操作系统中线程操作对临界资源进行赋初值操作,当资源数量小于等于0时再次请求资源则进行等待)

信号灯法:用一个变量赋予真假或0、1来对方法内部进行判断加锁。(如设置boolean flag来进行判断)

1.15. 线程池

可以存放线程,避免每次都要新建和销毁线程,提高开发效率,同时对线程进行管理。

ExecutorService 线程池名 = Executors.newFixedThreadPool(线程池大小即线程数量);//创建线程池
线程池名.execute(new 线程名);//加入线程
线程池名.shutdown();//关闭线程池
posted @ 2021-03-30 23:08  St0n3  阅读(62)  评论(0)    收藏  举报