线程
首先要了解进程和线程的区别:
进程是唯一的,一个进程可以开启多个线程。
比如:你的电脑开机了,你任务管理器里面运行的程序就是线程
1.线程的生命周期有五种状态:
创建、就绪、运行、阻塞、死亡
1.1 创建(创建方式往下找):
创建一个线程,线程进入创建状态。
1.2 就绪:
start()方法用于使一个线程进入就绪状态。
1.3 运行:
执行run()方法使线程进入运行状态
1.4 阻塞:
wait(等待)会让线程进入阻塞状态,会失去所占用的资源;
还有其他方式可以让一个线程进入阻塞状态(三个例子):
1.4.1 synchronized : 锁,同时调用同一个资源时,会等待正在执行的任务完成后在执行另一个任务。
1.4.2 sleep(睡眠):会让线程进入以毫秒为单位的休眠时间
1.4.3 join():会让主线程等待子线程执行完毕后再执行
join()方法例子:
没有使用join()方法前:
使用join()方法后:
结论:可以看出,在使用join方法之前,子线程和主线程是混在一块执行的,加入join方法之后,主线程会等待子线程执行完毕后再执行。
1.5 死亡(就是线程任务结束)
不要使用stop去让一个线程进入死亡状态,因为它已经被弃用了(可以完全不用这个方法,但可以知道有这个方法),这是Java开发者说的:
这种方法本身就不安全。使用thread停止一个线程。Stop会使它解除锁定的所有监视器的缺失(例如;未检查的ThreadDeath异常沿堆栈向上传播的自然结果)。如果以前受这些监视器保护的任何对象处于不一致的状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。stop的许多用法应该替换为简单地修改一些变量来指示目标线程应该停止运行的代码。目标线程应该定期检查这个变量,如果变量表明它将停止运行,则按顺序从它的run方法中返回。如果目标线程等待lona period(在一个条件变量上)。例如),应该使用中断方法来中断等待。
那么想让一个线程正常结束,就需要在方法内码如条件,比如:
@Override public void run() { for (int i=0;i<1000;i++){ System.out.println(str+i); } }
这个方法会在for循环1000次后结束。
另外,需要特别注意误区! ! ! ! !
start(就绪)和run(运行)方法虽然都可以运行线程,但是不一样的
start是说这个线程已经准备就绪,等待系统调度,获取资源后会自动执行run方法;
而run是一个普通的方法,调用的话会直接执行,并不会开启线程*
流程图
由上图可见,run方法直接跳过了系统调度这一环节
不要搞混了!!!!!!!
2.应用场景
当我们需要在同一时间执行两个或多个方法的时候,就可以使用线程。
比如:
当我们的切面中需要记录日志,如果方法的运行和记录日志同时执行的话,可能会影响项目整体的运行效率,那么 这时就可以在开启一个线程,同时进行登录和日志记录。
再比如:
登录时如果需要添加账号,如果查询账号花费大量的时间,会影响用户的体验,那么就可以开启一个线程专门去查 询和添加数据,而主线程继续进行页面的跳转,大大的优化项目效率。
看项目需求灵活使用
3.线程的创建(常用两种方法)
3.1继承Thread类并重写run方法
public class TestThread extends Thread{ @Override public void run() { System.out.println("Thread运行"); } }
主进程中调用:
TestThread testThread = new TestThread(); testThread.start();
运行结果
3.2实现Runnable接口并实现他的run方法
public class TestRunnable implements Runnable{ @Override public void run() { System.out.println("Runnable运行"); } }
需要注意:由于Runnable没有start()方法(Runnable全部源码)
@FunctionalInterface public interface Runnable { public abstract void run(); }
而开启一个线程就必须要使用start,所以就需要使用Thread:
//可以这么些 //实例化一个Runnable方法 TestRunnable testRunnable = new TestRunnable(); //new一个Thread传参为Runnable new Thread(testRunnable).start(); //或者这么写 new Thread(new Runnable() { @Override public void run() { System.out.println("Runnable运行"); } }).start(); //这还有流式的写法(可以了解一下) new Thread(() -> System.out.println("Runnable运行")).start();
最后运行结果:
两种方式的最大区别在于:
由于Java不支持多继承,而继承了Thread就不能继承其他的,所以一般都使用实现Runnable的方式去创建线程
还有一点
一个线程被start一次以后,就不可以再次start了,否则会报错哟
4.同步锁(synchronized)
被关键字修饰的资源同一时间不可以被其他资源访问,必须等待其他资源任务执行完成后再进行访问
(可以看下StringBuffer和StringBuilder的源码
StringBuffer的底层几乎都是加了synchronized同步锁,所以他的线程是安全的;
而StringBuilder则相反没有,所以他的线程是不安全的;
但这样也导致了StringBuffer的运行效率较StringBuilder而言较低)
synchronized举例说明:
//一个上厕所的方法;只有一个厕所 public static String 厕所(String name){ //上厕所5分钟 for (int i=0;i<5;i++){ //这里是为了方便观察才进行睡眠 if (i==3){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name+"正在厕所"); } return null; }
main方法
public static void main(String[] args) { //在这里开了四个线程,去厕所 new Thread(new Runnable() { @Override public void run() { 厕所("我"); } }).start(); new Thread(new Runnable() { @Override public void run() { 厕所("彭于晏"); } }).start(); new Thread(new Runnable() { @Override public void run() { 厕所("吴彦祖"); } }).start(); new Thread(new Runnable() { @Override public void run() { 厕所("胡歌"); } }).start(); }
程序运行结果:
由输出结果可见,四个线程在一起调用同一个方法,方法只有一个,到最后一起执行
四个人一起上厕所,厕所只有一个,四个人都往里钻,造成混乱
那么如果在代码中加入同步锁(synchronized)
//一个上厕所的方法;只有一个厕所 public static synchronized String 厕所(String name){ //上厕所5分钟 for (int i=0;i<5;i++){ //这里是为了方便观察才进行睡眠 if (i==3){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name+"正在厕所"); } return null; }
现在的运行结果:
非常和谐。
待续.....