6.1 线程
6.1.1 原理(1)创建线程
线程必须有一个名字,否则会抛出异常:
if (name == null) {
throw new NullPointerException("name cannot be null");
}
平时用Thread()创建线程的时候也没有指定名字,为什么没有报错?那是因为java默认会指定一个名字。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
默认的名字就是"Thread-" + nextThreadNum(),
创建几个线程,然后打印一下名字:
线程1:Thread-0
线程2:Thread-1
...
(2)执行线程
public synchronized void start()
synchronized关键字表示此方法是同步的,同一时间内,只能有一个代码来调用start()
默认threadStatus = 0
if (threadStatus != 0)
throw new IllegalThreadStateException();
!不能多次启动一个线程。会报出异常:
Exception in thread "main" java.lang.IllegalThreadStateException
(3)休眠
以指定的毫秒休眠
public static void sleep(long millis)
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
其中millis不能小于0,负责抛出异常:
Exception in thread "Thread-0" java.lang.IllegalArgumentException: timeout value is negative
(4)终止线程
public class Test extends Thread
{
@Override
public void run()
{
while (true)
{
try
{
System.out.println("running...");
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("stop");
}
}
}
然后调用:
public static void main(String[] args)
{
Test test1 = new Test();
test1.start();
for(int i=1;i<1000000;i++){
}
test1.stop();
}
会发现只是输出了running...,而终止的时候并没有输出stop,
API中已不建议使用stop()来终止线程,如果线程中有其他操作,突然终止线程的话,会带来一些意外的结果。
安全终止线程的方法:通过标记
public class Test extends Thread
{
public boolean flag = true;
@Override
public void run()
{
while (flag)
{
try
{
System.out.println("running...");
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("stop");
}
}
public void setFlag(boolean flag)
{
this.flag = flag;
}
}
终止线程:setFlag(false);
6.1.2 多线程的执行顺序
Test test1 = new Test("1");
test1.start();
Test test2 = new Test("2");
test2.start();
输出:
running...1
running...2
running...2
running...1
running...2
running...1
可以看到两个线程的执行顺序不是固定不变的,执行哪个线程是由JVM决定的。可以通过setPriority来影响执行顺序。
测试:
Test test1 = new Test("1");
test1.start();
Test test2 = new Test("2");
test2.setPriority(Thread.MIN_PRIORITY);
test2.start();
Test test3 = new Test("3");
test3.start();
输出:
...
running...2
running...3
running...1
...
!在单核CPU上多任务的执行其实是轮流执行,在一个时间点上,CPU只能执行一个任务,比如一边看电影,一边听歌,CPU加载一点电影,然后去加载一点歌曲,如此反复,
因为每次执行的时间很短,就造成了同步执行任务的假象。
以windows为例在操作系统级别,每个线程最大运行时间大概是20ms,也就是操作系统的时间片是20ms,线程根据时间片来运行。假如到了20ms,cpu将切换到下一个线程执行。
当再次切换回来的时候,cpu按照上次执行的进度继续执行该线程。
6.2 进程
进程是程序的执行过程,从加载到执行,最后结束的一个完整的过程。
线程是让代码并发执行的一个手段。
比如在windows任务管理器中可以看到有很多进程,有时一个软件有很多进程,
例如Chrome浏览器就有很多进程,包含了软件的核心进程和页面进程。每打开一个新网页,就会开辟一个进程用于次网页。
当一个网页的进程阻塞或者崩溃后,不会影响其它进程。
这和线程是一个道理。一些耗时的操作,一般不会在主线程中运行,而是新开一个线程运行,这样就不会阻塞主线程。
6.3 并发
多个线程同时访问一个资源产生并发。假如8888的账户有100块,A和B两个人同时在2个取款机上取钱:
服务器:A要在8888账户取100,余额够么?
数据库:够。
服务器:B要在8888账户取100,余额够么?
数据库:够。
服务器:A取走了,你改下数据。
数据库:OK。
服务器:B取走了,你改下数据。
数据库:A刚才不是把8888取光了么,你个傻B。
服务器:额。。。
解决这个问题的方法就是加锁。A操作的时候锁住这个账户的数据,等A的操作,取钱,存钱,等,修改完数据库以后,B的操作再进行,
如果A取完了账户中的钱,那么给B显示余额不足。
加锁在java中用synchronized关键字。
public class Main
{
public static void main(String[] args)
{
Test1 t1 = new Test1();
Test1 t2 = new Test1();
t1.start();
t2.start();
}
static class Test1 extends Thread
{
@Override
public void run()
{
while (true)
{
try
{
//synchronized (this)
{
System.out.println("running 1");
System.out.println("running 2");
System.out.println("running 3");
}
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
结果:
running 1
running 1
running 2
running 2
running 3
running 3
running 1
running 2
running 3
...
没有加锁的情况下,每个线程都可以随时访问该代码块。
加锁以后,结果:
running 1
running 2
running 3
running 1
running 2
running 3
running 1
running 2
running 3
...
同一个时间只有一个线程可以访问该代码块。别的线程想访问就必须排队。