java 线程的基本知识整理

创建线程的五种方式

继承Thread

1.继承Thread并实现它的方法run(),方法里面是我们给线程的任务

static class Dee implements Thread {
		@Override
		public void run() {System.out.println("as");}
}
public static void main(String[] args) {
	    Dee dee = new Dee();
	    //线程启动
	    dee.start();
}

实现Runnable接口

2.Runnable接口,相当于创建一个任务,我们要执行这个任务的时候,就创建一个线程,然后传入这个任务—给线程一个任务

static class Dee implements Runnable {
        @Override
        public void run() {System.out.println("as");}
}
public static void main(String[] args) {
        Dee dee = new Dee();
        //通过构造方法将任务对象dee传入线程
        Thread t1 = new Thread(dee);
        t1.start();
}

匿名类实现

3.匿名类实现,只能创建一个执行指定任务的线程,若要创建多个重复任务的线程,则不该采取这种方式创建线程

public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("as");
            }
        };
        thread.start();
 /************************************************/
 //极端情况
         new Thread(){
            @Override
            public void run() {
                System.out.println("as");
            }
        }d.start();
}

实现Callable接口

Callable带返回值的线程

public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Boolean> dee = new Dee();
        FutureTask<Boolean> futureTask = new FutureTask<>(dee);
        Thread thread = new Thread(futureTask);
        //判断任务是否执行完毕
        futureTask.isDone();
        //可以主动取消任务,参数为true表示取消,返回值是boolean,返回true表示取消成功
        futureTask.cancel(true);
        thread.start();
        //要先将线程启动,才能调用get方法,否则会阻塞在那里
        boolean b = futureTask.get();
        System.out.println(b);
    }
    static class Dee implements Callable<Boolean> {
        @Override
        public Boolean call() throws Exception {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println(i);
            }
            return true;
        }
    }

线程池

线程池–可以使线程重复使用,它也是一个线程,当任务执行完成之后,程序不会马上关闭,就是因为线程池还在等待接收,但若没有任务,一段时间之后会自动关闭。

缓存线程池,若线程池容量不足,扩容
ExecutorService service = Executors.newCachedThreadPool();
service.execute(任务对象(实现接口Runnable,重写run方法));

定长线程池,任务排队执行
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(任务对象(实现接口Runnable,重写run方法));

单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(任务对象(实现接口Runnable,重写run方法));

周期定长线程池
ScheduleExecutorService service = Executors.newScheduledThreadPool();
service.schedule(三个参数【自己看一下说明】)
service.scheduleAtFixedRate(四个参数)

线程的常见方法

设置和获取线程名称

1.设置和获取线程名称的两个方法——setName、getName

public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
            						//得到当前线程的名称
                System.out.println(Thread.currentThread().getName());
            }
        };
        //设置线程的名称
        thread.setName("110");
        thread.start();
}

结果:

110

线程休眠

2.线程休眠方法——sleep

Thread thread = new Thread('传入某个任务');
thread.sleep(1000);//休眠1000毫秒即1秒

线程的中断

3.线程的中断:由线程自己决定是否中断。
因为若在外部直接掐死该线程,那么该线程就来不及释放某些资源,这将会导致内存垃圾,且垃圾可能无法被回收。
我们可以给线程(不是给任务对象)—打上中断标记。
然后当run方法里当调用sleep或wait方法时,线程会查看一下中断标记,
若标记为true,我们可以直接reuturn就结束线程,当然我们也可以不理会这个标记。
(注意当线程接收到标记为true,除了我们可以做些处理之外,该标记会自动改为false)

    public static void main(String[] args) throws InterruptedException {
        Dee dee = new Dee();
        Thread thread1= new Thread(dee);
        thread1.start();
        for (int i=0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        //给线程打上中断标记
        thread1.interrupt();
    }
    //我们采用上面所说的创建线程的第二种方式,所以创建一个类实现Runnable接口
    static class Dee implements Runnable {
        @Override
        public void run() {
            for (int i=0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                //当我们被打上标记,就会运行这里的代码块
                    System.out.println("我们被打上中断标记了,不管,我们改回来!");
                    //我们其实什么都没做,它自己就会改回来了
                    System.out.println("现在标记为:" + Thread.currentThread().isInterrupted());
                }
                System.out.println(Thread.currentThread().getName()+"---"+i);
            }
        }
    }

结果:

Thread-0---0
main:0
Thread-0---1
main:1
Thread-0---2
main:2
Thread-0---3
main:3
main:4
Thread-0---4
我们被打上中断标记了,不管,我们改回来!
现在标记为:false
Thread-0---5
Thread-0---6
Thread-0---7
Thread-0---8
Thread-0---9

守护线程

4.守护线程:调用setDaemon(true);方法即可将线程设为守护线程,(注意参数为true时,才为守护线程)
用户线程:当进程里的所有用户线程结束,进程结束
守护线程:当所有用户线程结束,它就自动死亡

    public static void main(String[] args) throws InterruptedException {
        //通过匿名类实现来创建用户线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }.start();
        //创建守护线程
        Thread daemonThread = new Thread() {
            @Override
            public void run() {
            //设为无限循环,来试试守护线程是否会因为用户线程的结束而自动结束
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("我还活着。。。haha");
                }
            }
        };
        //设置该线程为守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }

结果:

我还活着。。。haha
Thread-0: 0
我还活着。。。haha
Thread-0: 1
我还活着。。。haha
Thread-0: 2
我还活着。。。haha
Thread-0: 3
我还活着。。。haha
Thread-0: 4

我们可以注意到,当用户线程结束后,守护线程也自动结束了,尽管它的run()方法是while(true)来进行无限循环,但可惜,一旦当所有的用户线程结束后,它也会自动结束。

同步代码块和同步方法

5.同步代码块和同步方法:要有锁对象的概念
当线程想要执行被锁对象锁住的代码块,就必须得到锁对象。若锁对象被其他线程抢到,则该线程等待,直到下次抢夺开始。抢到了就执行代码块,没抢到就继续等待,直到抢到为止。

下面就是由两个线程来输出0到4的数字,规则:必须有序且不重复。

    public static void main(String[] args) throws InterruptedException {
        Dee dee = new Dee();
        Thread t1 = new Thread(dee);
        Thread t2 = new Thread(dee);
        t1.start();
        t2.start();
    }
    static class Dee implements Runnable {
        private Object object = new Object();
        private int i;
        @Override
        public void run() {
            for (i = 0; i < 5; i++) {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

结果:

Thread-0: 0
Thread-1: 0
Thread-1: 2
Thread-0: 2
Thread-1: 4
Thread-0: 4

以上的案例出现错乱,是因为for循环时,两个判断的时机不对。
案例分析:
当线程A判断循环,此时i=0,i<5,判断可以进,线程A就执行循环体里的代码。
但几乎时同一时刻,线程B也在判断,而线程A并没有走完循环,好让i=1。所以此时线程B判断的也是i=0,i<5,可以进。
此时线程A可能刚println打印完Thread-0: 0,正在休眠中。紧接着线程B也开始打印,此时线程A还没有第二次循环,说明i还是等于0,所以线程B也是输出Thread-1: 0。

以下是正确的案例:

static class Dee implements Runnable {
        private Object object = new Object();
        //若是不赋值,默认为0
        private int i;
        @Override
        public void run() {
            while (true) {
            //我们用object这个对象来做锁对象
                synchronized (object) {
                    if (i >= 5) break;
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                    i++;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

结果:

Thread-0: 0
Thread-1: 1
Thread-1: 2
Thread-0: 3
Thread-1: 4

由以上的两个案例,可以提示到我们,当我们使用同步方法或代码块的时候就要慎重考虑采用for,还有仔细思考不同线程执行的时机。
同步方法的案例:

    static class Dee implements Runnable {
        //若是不赋值,默认为0
        private int i;
        @Override
        public void run() {
            while (true) {
                if (print()) break;
                try {
                	Thread.sleep(10);
            	} catch (InterruptedException e) {
                	e.printStackTrace();
            	}
            }
        }
        public synchronized boolean print() {
            if (i >= 5) return true;
            System.out.println(Thread.currentThread().getName() + ": " + i);
            i++;
            return false;
        }
    }

注意:同步方法锁的是this,即该任务对象。

显示锁

Lock接口可以new一个它的子类ReentrantLock得到显示锁。

    public static void main(String[] args) throws InterruptedException {
        Dee dee = new Dee();
        Thread t1 = new Thread(dee);
        Thread t2 = new Thread(dee);
        t1.start();
        t2.start();
    }
    static class Dee implements Runnable {
        //若是不赋值,默认为0
        private int i;
        //声明一个显示锁
        Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true) {
                l.lock();
                if (i >= 5) return;
                System.out.println(Thread.currentThread().getName() + ": " + i);
                i++;
                l.unlock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

结果:

显示锁错误案例
原因是当我们的 if (i >= 5)为真时,return。此时虽然该线程结束了,但是他并没有释放锁对象,导致另一个线程一直在阻塞。

正确的案例:

static class Dee implements Runnable {
        //若是不赋值,默认为0
        private int i;
        //声明一个显示锁
        Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true) {
                l.lock();
                if (i >= 5){
                    l.unlock();
                    return;
                }
                System.out.println(Thread.currentThread().getName() + ": " + i);
                i++;
                l.unlock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

结果:

Thread-1: 0
Thread-0: 1
Thread-1: 2
Thread-0: 3
Thread-1: 4

公平锁:线程排队,不会出现一个线程连续抢到cpu时间片

static class Dee implements Runnable {
        //若是不赋值,默认为0
        private int i;
        //声明一个显示锁
        Lock l = new ReentrantLock(true);
        @Override
        public void run() {
            while (true) {
                l.lock();
                if (i >= 5){
                    l.unlock();
                    return;
                }
                System.out.println(Thread.currentThread().getName() + ": " + i);
                i++;
                l.unlock();
            }
        }
    }

结果:

Thread-1: 0
Thread-0: 1
Thread-1: 2
Thread-0: 3
Thread-1: 4

注意:以上程序的sleep语句,除了中断标记的方法要用到,其他的都是为了要差距明显,体现线程抢占式的cpu时间分配。
补充:线程的阻塞,当因某件事情比较消耗时间就可称之为阻塞:比如读取文件,或等待用户输入等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值