JAVA 多线程

例子均来自 并发编程的优缺点 以及 线程的状态转换以及基本操作,记录一下,怕忘了之后找不到……

1.新建线程

一个java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上java程序天生就是一个多线程程序,包含了:
(1)分发处理发送给给JVM信号的线程;
(2)调用对象的finalize方法的线程;
(3)清除Reference的线程;
(4)main线程,用户程序的入口。
那么,如何在用户程序中新建一个线程了,有三种方式:
(1)通过继承Thread类,重写run方法;
(2)通过实现runable接口;
(3)通过实现callable接口这三种方式。

demo如下:

public class CreateThreadDemo {

     public static void main(String[] args) {
         //1.继承Thread
         Thread thread = new Thread() {
             @Override
             public void run() {
                 System.out.println("继承Thread");
                 super.run();
             }
         };
         thread.start();
         //2.实现runable接口
         Thread thread1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("实现runable接口");
             }
         });
         thread1.start();
         //3.实现callable接口
         ExecutorService service = Executors.newSingleThreadExecutor();
         Future<String> future = service.submit(new Callable() {
             @Override
             public String call() throws Exception {
                 return "通过实现Callable接口";
             }
         });
         try {
             String result = future.get();
             System.out.println(result);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }

 }
2.死锁
public class DeadLockDemo {
    private static String resource_a = "A";
    private static String resource_b = "B";

    public static void main(String[] args) {
        deadLock();
    }

    public static void deadLock() {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_a) {
                    System.out.println("get resource a");
                    try {
                        Thread.sleep(3000);
                        synchronized (resource_b) {
                            System.out.println("get resource b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_b) {
                    System.out.println("get resource b");
                    synchronized (resource_a) {
                        System.out.println("get resource a");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();

    }
}

原因:threadA占用了resource_a, 并等待被threadB释放的resource _b。threadB占用了resource _b正在等待被threadA释放的resource _a。因此threadA,threadB出现线程安全的问题,形成死锁。

通常可以用如下方式避免死锁的情况:
(1)避免一个线程同时获得多个锁;
(2)避免一个线程在锁内部占有多个资源,尽量保证每个锁只占用一个资源;
(3)尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
(4)对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

3.interrupted

中断可以理解为线程的一个标志位,它表示了一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了一个招呼。其他线程可以调用该线程的interrupt()方法对其进行中断操作,同时该线程可以调用 isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。另外,同样可以调用Thread的静态方法 interrupted()对当前线程进行中断操作,该方法会清除中断标志位。需要注意的是,当抛出 InterruptedException 时候,会清除中断标志位,也就是说在调用isInterrupted 会返回false。
demo:

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        //sleepThread睡眠1000ms
        final Thread sleepThread = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                super.run();
            }
        };
        //busyThread一直执行死循环
        Thread busyThread = new Thread() {
            @Override
            public void run() {
                while (true) ;
            }
        };
        sleepThread.start();
        busyThread.start();
        sleepThread.interrupt();
        busyThread.interrupt();
        while (sleepThread.isInterrupted()) ;
        System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
        System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
    }
}

输出结果

sleepThread isInterrupted: false busyThread isInterrupted: true

开启了两个线程分别为 sleepThread 和 BusyThread, sleepThread 睡眠 1s,BusyThread 执行死循环。然后分别对着两个线程进行中断操作,可以看出sleepThread 抛出 InterruptedException 后清除标志位,而busyThread就不会清除标志位。
另外,同样可以通过中断的方式实现线程间的简单交互, while (sleepThread.isInterrupted()) 表示在Main中会持续监测 sleepThread,一旦sleepThread 的中断标志位清零,即 sleepThread.isInterrupted() 返回为 false 时才会继续 Main 线程才会继续往下执行。因此,中断操作可以看做线程间一种简便的交互方式。一般在结束线程时通过中断标志位或者标志位的方式可以有机会去清理资源,相对于武断而直接的结束线程,这种方式要优雅和安全。

4.join

如果一个线程实例A执行了 threadB.join(),其含义是:当前线程A会等待 threadB 线程终止后 threadA 才会继续执行;另外还提供了超时等待的方法,如果线程 threadB在等待的时间内还没有结束的话,threadA 会在超时之后继续执行。
join 方法一共提供如下这些方法:

public final synchronized void join(long millis)
public final synchronized void join(long millis, int nanos)
public final void join() throws InterruptedException

demo:

public class JoinDemo {
    public static void main(String[] args) {
        Thread previousThread = Thread.currentThread();
        for (int i = 1; i <= 10; i++) {
            Thread curThread = new JoinThread(previousThread);
            curThread.start();
            previousThread = curThread;
        }
    }

    static class JoinThread extends Thread {
        private Thread thread;

        public JoinThread(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
                System.out.println(thread.getName() + " terminated.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果:

main terminated.
Thread-0 terminated.
Thread-1 terminated.
Thread-2 terminated.
Thread-3 terminated.
Thread-4 terminated.
Thread-5 terminated.
Thread-6 terminated.
Thread-7 terminated.
Thread-8 terminated.

在上面的例子中一个创建了10个线程,每个线程都会等待前一个线程结束才会继续运行。

5. sleep() VS wait()

两者主要的区别:

(1)sleep()方法是Thread的静态方法,而wait是Object实例方法
(2)wait() 方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而 sleep() 方法没有这个限制可以在任何地方种使用。另外,wait() 方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而 sleep() 方法只是会让出 CPU 并不会释放掉对象锁;
(3)sleep() 方法在休眠时间达到后如果再次获得 CPU 时间片就会继续执行,而wait() 方法必须等待 Object.notift/Object.notifyAll 通知后,才会离开等待池,并且再次获得 CPU 时间片才会继续执行。

6. yield()

public static native void yield(); 这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。
在 Java 程序中,通过一个整型成员变量 Priority 来控制优先级,优先级的范围从1~10.在构建线程的时候可以通过 setPriority(int) 方法进行设置,默认优先级为5,优先级高的线程相较于优先级低的线程优先获得处理器时间片。需要注意的是在不同JVM以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。
另外需要注意的是,sleep() 和 yield() 方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep() 交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而 yield() 方法只允许与当前线程具有相同优先级的线程能够获得释放出来的 CPU 时间片。

7.守护线程

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT 线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退出。当一个 Java 应用,只有守护线程的时候,虚拟机就会自然退出。下面以一个简单的例子来表述 Daemon 线程的使用。

public class DaemonDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("i am alive");
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        System.out.println("finally block");
                    }
                }
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();
        //确保main线程结束前能给daemonThread能够分到时间片
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

i am alive
finally block
i am alive

上面的例子中 daemodThread run 方法中是一个 while 死循环,会一直打印,但是当main 线程结束后 daemonThread 就会退出所以不会出现死循环的情况。main 线程先睡眠 800ms 保证 daemonThread 能够拥有一次时间片的机会,也就是说可以正常执行一次打印 “i am alive” 操作和一次 finally 块中 “finally block” 操作。紧接着 main 线程结束后,daemonThread 退出,这个时候只打印了 “i am alive” 并没有打印finnally 块中的。因此,这里需要注意的是守护线程在退出的时候并不会执行 finnaly块中的代码,所以将释放资源等操作不要放在 finnaly 块中执行,这种操作是不安全的。
线程可以通过 setDaemon(true) 的方法将线程设置为守护线程。并且需要注意的是设置守护线程要先于start()方法,否则会报错:

Exception in thread “main” java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1365)
at learn.DaemonDemo.main(DaemonDemo.java:19)

这样的异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值