学习书籍《java多线程编程核心技术(第二版)-高洪岩》
初识线程
public class HelloMain {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
上面解释为:名称为main的线程在实现main()方法中的代码
使用多线程(1.继承Thread类,2.实现Runnable接口)
打开源码我们发现
Thread类竟然实现了Runnable接口。
所以我们有多种方法创建线程对象,其结果都是一样的,即创建一个线程
1.永Thread类创建对象
2.子线程类MyThread继承父线程类Thread,然后创建对象
3.子线程类实现Rubbable接口,然后创建对象
public class HelloMain {
public static void main(String[] args) {
Runnable t1 = new Thread();
Runnable t2 = new MyThread();
Thread t3 = new MyThread();
t3.start();
System.out.println("hello main");
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread");
}
}
}
上面代码永start()方法来启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法里面的代码就是线程对象要执行的任务。
结果运行10次都是下面的情况
对于上面的代码,明明先执行t3.start,再执行sout,但是结果却是先输出sout在执行t3.start
下面给出原因
执行start()时要有以下步骤导致耗时,以至于系统先执行了后面的sout代码
1.告诉jvm创建Thread
2.操作系统开辟内存创建线程对象
3.操作系统调度线程
4.线程被执行
可以使用sleep函数,结果会不同。
查看线程
public class Run3 {
public static void main(String[] args) {
for (int i=0;i<5;i++){
new Thread(){
@Override
public void run() {
try {
Thread.sleep(500000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}.start();
}
}
}
创建5个线程,每个线程500秒的sleep
查看方式有三种:
第一种:
打开jkd的安装目录的bin的文件夹
我的是
jps+jstack.exe
第二种(推荐使用)
双击jmc.exe,关闭欢迎页面,进入你的线程类
我的是
双击MBean服务器
你能看到java虚拟机的一些参数。
第三种
双击jvisualvm.exe,和第二种差不多
线程随机性的体现
public class MyThread extends Thread {
@Override
public void run() {
for (int i=0;i<10000;i++){
System.out.println("thread"+Thread.currentThread().getName());
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i=0;i<10000;i++){
System.out.println("main"+Thread.currentThread().getName());
}
}
}
部分结果
start()方法,通知线程规划器,此线程已经准备好,准备调用run方法,系统你来安排时间调用。
同时也说明,线程的启动顺序与线程的执行顺序是不同的。
到此为止,我是这样想的,java虚拟机是一个操作系统的进程,当执行该Test类的main方法时,会创建一个线程,该线程的名字是main,当我们在执行main线程的时候,又创建了一个MyThread,此时的虚拟机进程又会创建一个新的线程同时和已经存在的main线程同时执行
执行start()的顺序不代表执行run()的顺序
public class MyThread extends Thread {
private int i;
public MyThread(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread(1);
MyThread myThread2 = new MyThread(2);
MyThread myThread3 = new MyThread(3);
MyThread myThread4 = new MyThread(4);
MyThread myThread5 = new MyThread(5);
myThread1.start();
myThread2.start();
myThread3.start();
myThread4.start();
myThread5.start();
}
}
结果
实现runnable接口(推荐使用)
因为runnable是一个接口,所以不能直接new,要用它的实现类来实现
1.创建一个类(可以是Thread的子类)实现Runnable接口
2.创建该实现类的对象
3.用Thread类的构造函数,传入该对象,然后构造一个线程对象
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("myRunnable");
}
}
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("mythread");
}
}
public class Test {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
// ----------------------------------
Thread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
thread1.start();
}
}
结果
线程共享实例变量和线程不共享实例变量的区别
1.线程不共享变量情况(每个线程都有自己的变量,其他线程修改同样的变量时,自己的变量值不变)
public class MyThread implements Runnable{
private String name;//线程名称
private int count = 3;//表示该线程内的变量,与其他线程无关
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while (count>0){
System.out.println(this.name+":"+count);
count--;
}
}
}
public class Test {
public static void main(String[] args) {
Runnable runnable1 = new MyThread("A");
Runnable runnable2 = new MyThread("B");
Runnable runnable3 = new MyThread("C");
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
Thread thread3 = new Thread(runnable3);
thread1.start();
thread2.start();
thread3.start();
}
}
结果
1.线程共享变量情况(多个线程访问一个变量),非线程安全:多个线程对同一对象中的变量进行操作时出现的值不同步现象
public class MyThread1 implements Runnable{
private int count = 5;
@Override
public void run() {
//先执行count--,再执行sout
System.out.println("count:"+count--);
}
}
public class Test1 {
public static void main(String[] args) {
Runnable runnable = new MyThread1();
Thread threadA = new Thread(runnable, "A");
Thread threadB = new Thread(runnable, "B");
Thread threadC = new Thread(runnable, "C");
Thread threadD = new Thread(runnable, "D");
Thread threadE = new Thread(runnable, "E");
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
}
}
结果,不仅共享了一个变量,而且线程不安全
如果在run方法加上synchronized关键字,让其排队执行,一个线程调用run()方法之前,先判断run方法有没有被加上锁,如果有锁,则不断尝试申请这把锁,必须等其他线程调用run执行完并且释放锁之后,才可以执行该方法,并为该方法加上锁。多个线程都会取申请这把锁。
结果:
线程安全,抢票
public class MyThread extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
--count;
System.out.println(this.currentThread().getName()+"得到一张,还剩"+count+"张");
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread, "1");
Thread thread2 = new Thread(myThread, "2");
Thread thread3 = new Thread(myThread, "3");
Thread thread4 = new Thread(myThread, "4");
Thread thread5 = new Thread(myThread, "5");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
结果
解决办法,
加上synchronized,使多个线程在执行run的时候,以排队的方式进行处理。互斥锁。
如果申请不到锁就会自循环。多个线程同时抢锁。
currentThread()方法:该方法表示正在被哪个线程调用
public class MyThread implements Runnable{
public MyThread() {
System.out.println("构造方法的执行线程:"+Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("线程run方法的执行线程:"+Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
Runnable runnable = new MyThread();
Thread thread = new Thread(runnable,"thread_name_xxx");
thread.start();
}
}
结果:
isAlive方法:判断当前线程是否存活(线程已经启动尚未终止即为存活状态)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.isAlive());
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("begin"+myThread.isAlive());
myThread.start();
Thread.sleep(1000);
System.out.println("end="+myThread.isAlive());
}
}
sleep(long毫秒)方法:在指定的时间内让【当前正在执行的线程:Thread.currentThread()返回的线程】暂停执行
也有slee(long毫米,int纳秒)
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+ System.currentTimeMillis());
try {
this.sleep(1000);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ System.currentTimeMillis());
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
System.out.println(Thread.currentThread().getName()+":"+ System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+":"+ System.currentTimeMillis());
}
}
结果:如果调用类是Thread,可以用this.sleep或者Thread.sleep
如果是非Thread类(包含Runnable的继承类),只能用Thread.sleep
停止与启动(start)线程
一般不用Thread.stop(),用Thread.interrupt()方法。
stop:该方法不安全,并且已经作废
interrupt:不会终止一个正在运行的线程,需要加入一个判断才可以完成线程的停止
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
myThread.interrupt();
System.out.println("zzzzzzz");
}
}
结果
interrupt并不能马上停止线程,而是在当前线程中做了一个停止的标记。
this.interrupt():测试当前线程是否中断,执行后清除标志位,返回false
this.isInterrupt();测试线程对象是否中断,不清除标志位。
停止线程方法1(异常法)建议使用
public class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
for (int i = 0; i <500000 ; i++) {
if(this.interrupted()){
System.out.println("stoped");
throw new InterruptedException();
}
System.out.println(i);
}
System.out.println("for");
}catch (InterruptedException e){
System.out.println("catch");
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(1000);
myThread.interrupt();
}catch (InterruptedException e){
System.out.println("main catch");
e.printStackTrace();
}
}
}
线程停止-异常法
1.停止不了的线程(用interrupt方法),例如
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
if(this.isInterrupted()){
System.out.println("线程已经停止,我要break了");
break;
}
System.out.println(i);
}
System.out.println("虽然线程已经停止,但是我还能运行");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
Thread.sleep(500);
thread.interrupt();
System.out.println("这是main线程");
}
}
结果:虽然我们调用了,interrupt方法,但是for之外的代码还是继续运行,break无效
2.异常法(抛出异常)停止线程,还是调用interrupt方法
public class MyThread extends Thread{
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
if(this.isInterrupted()){
System.out.println("线程已经停止,我要break了");
throw new InterruptedException("手动抛出异常");
}
System.out.println(i);
}
System.out.println("虽然线程已经停止,但是我还能运行");
}catch (InterruptedException e){
System.out.println("进入MyThread类的catch方法");
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args){
try {
Thread thread = new MyThread();
thread.start();
Thread.sleep(500);
thread.interrupt();
}catch (InterruptedException e){
System.out.println("进入Test类的catch方法");
e.printStackTrace();
}
System.out.println("这是main线程");
}
}
结果:
线程在sleep状态时执行interrupt方法,或者在执行interrupt方法之后又执行sleep方法,总之:不管这两个方法调用顺序如何,都会进入异常
1.先sleep后interrupt
public class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程在sleep,执行interrupt,进入InterruptedException");
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
thread.interrupt();
}
}
结果:
2.先interrupt后sleep
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
System.out.println(i);
}
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程在sleep,执行interrupt,进入InterruptedException");
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
thread.interrupt();
}
}
结果:
return可以停止线程
暂停与恢复线程
suspend暂停,resume恢复 已经作废
可以用wait notify notifyAll(后面单独整理)
yield,放弃当前线程的cpu资源(有可能刚刚放弃,又马上自动获取cpu资源)
public class MyThread extends Thread {
@Override
public void run() {
long begintime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 20000000; i++) {
// Thread.yield();
count = count+i;
}
long endtime = System.currentTimeMillis();
System.out.println(endtime-begintime);
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
没有yield时,耗时7毫秒
有yield时,耗时2056毫秒
线程优先级1-10 正常5,
thread.setPriority();
1.优先级有继承性:A线程启动B线程,则B的优先级与A一样
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThord:"+this.getPriority());
}
}
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
System.out.println("main:"+thread.getPriority());
}
}
结果:
2.可以使用thread.setPriority();或者JDK默认数来设置优先级,数字越大,优先级越高
3.优先级高的先执行,但不是先全部执行完再执行优先级低的代码。只是cpu尽量将资源让给高优先级的线程。
守护线程(后面待研究)
1.java有两种线程:守护线程和用户线程(非守护线程)
2.用户线程:main,或自己创建的线程;守护线程:GC线程
3.当线程中不存在用户线程,守护线程也就自动销毁