写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!
线程的状态及转换:
对 wait()、sleep()、notifyAll() 的理解:
这三个方法是定义在Object类里的方法。
wait(): 使持有该对象的线程把该对象的控制权(锁)交出,然后处于等待状态。
notify(): 通知某个正在等待这个对象控制权的线程恢复运行。
notifyAll(): 通知所有等待这个对象控制权的线程恢复运行。
任何时候对象的控制权只能被一个线程拥有,在执行这三个方法时,首先应保证当前运行的线程获取了该对象的控制权,否则就会报java.lang.IllegalMonitorStateException异常。所以注意:这三个方法的使用需要在同步方法(代码块、实例)中使用,否则会出现问题。
对 wait()、sleep()、yield() 的理解:
wait():线程等待,交出对象控制权,即释放对象锁
sleep():线程休眠,一定时间后继续执行,并没有释放锁
yield():放弃执行,进入可执行状态,下次获取到cpu时间片可再执行,也没有释放持有的锁
拓展:
wait(long timeout):
官网有这么一段话:
当调用wait()方法时,使当前线程等待,直到另一个线程调用此对象的notify或notifyAll方法,或等待的时间已过,才会继续执行,准确的说notify也行并不能唤醒这个等待的线程,因为可能有多个线程都在等待这个对象的控制权,notify只会随机唤醒一个线程。
wait(0)同wait一样,线程只能是被唤醒后, 才能再次获取锁执行wait后续代码。
创建线程的三种方式
1):继承Thread 重写run方法 调用Thread实现类的start()方法启动线程
public class MyThread extends Thread {
public void run(){
System.out.println("111");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
2):实现Runnable 重写run方法 执行线程需要丢入runnable接口的实现类 调用start方法
public class MyThread implements Runnable {
public void run(){
System.out.println("111");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
3):实现Callable接口 重写call()方法 通过创建执行服务来执行目标线程
public class MyThread implements Callable {
@Override
public Object call() throws Exception {
return "111";
}
public static void main(String[] args) throws Exception{
MyThread thread1 = new MyThread();
ExecutorService service = Executors.newCachedThreadPool();
Future future = service.submit(thread1);
System.out.println(future.get());
service.shutdown();
}
}
继承和实现的方式创建线程的区别也是显而易见的,1)实现的方式可以避免java中单继承的限制,2)适合多个线程去处理一个资源。3)而相比于Runnable方式,实现Callable有返回值 ,Runnable方式没有返回值。
通过代码理解下它们的区别,看一下下面的例子:
public class TestThread {
public static void main(String[] args) {
new Test1("A").start();
new Test1("B").start();
new Test1("C").start();
}
}
class Test1 extends Thread{
private int ticket = 5;
public Test1(String s) {
this.setName(s);
}
@Override
public void run() {
while (true){
buy();
}
}
private void buy(){
if(ticket <= 0){
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到"+ticket--);
}
}
执行结果:
如上:我启动了三个线程ABC去买票,最终的执行结果其实是这三个线程分别去买了5张票。
上面说到,实现Runnable接口的方式适合多个线程去处理一个资源,看下下面的例子:
public class RunnableTest {
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy,"A").start();
new Thread(buy,"B").start();
new Thread(buy,"C").start();
}
}
class Buy implements Runnable{
private int ticket = 10;
@Override
public void run() {
while (true){
buy();
}
}
private synchronized void buy(){
if(ticket <= 0){
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到"+ticket--);
}
}
执行结果:
守护线程
线程其实分为用户线程和守护线程,想让一个线程成为守护线程只要通过线程的.setDaemon(true);方法设置即可。
虚拟机在运行的时候,必须保证用户线程执行完毕,并且不用等待守护线程执行完毕。可以理解为守护线程其实就是默默守护我们用户线程执行的线程,例如垃圾回收gc线程,我们甚至不用管他,只要咱们的用户线程执行完(main线程执行完),守护线程也会自动停止。通过下面的例子看一下:
public class TestThread {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);
thread.start();
Thread thread1 = new Thread(you);
thread1.start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("a");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("一切结束了");
}
}
先贴一下执行结果:
如上:如果不是设置为守护线程,thread线程应该会一直执行下去,控制台一直打印a,但是我们看到,线程thread在thread1执行完后也停止了。