线程的实现方式
实现线程的第一种方式
//实现线程的第一中方式,使用继承Thread来实现线程
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName().toString()+"_"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这样在mian方法中可以直接使用:
MyThread md = new MyThread();
md.start();
方式来启动线程
实现线程的第二种方式
//实现线程的第二个方式,实现Runable接口,并将其对象赋值给Thread来实现线程
//它将线程作为一个类传递过去
class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName().toString()+"_"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这样实现的线程, 在main方法中的调用方式是:
MyRunable mt = new MyRunable();
Thread t2 = new Thread(mt);
t2.start();
注:以上两种方法实现线程的main函数调用代码如下:
public static void main(String[] args) {
// MyThread md = new MyThread();
// md.start();//准备就绪,启动线程
MyRunable mt = new MyRunable();
Thread t2 = new Thread(mt);
t2.start();
}
线程的中断方式
用 interrupt 中断标记来中断线程
//用interrupt中断标记来中断线程
class MyRunnalbe2 implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++) {
//使用Thread.interrupted()方法测试中断状态, 此方法会清除中断标记
if(Thread.interrupted()) {
break;
}
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
//标记到此会抛出异常并清除标记,需要重新标记
Thread.currentThread().interrupt();
}
}
}
}
这样,在main函数中通过调用该线程的对象的interrupt方法:
t.interrupt();
来结束线程,这种方法需要在抛出异常的时候重新标记中断,所以不被使用
用自定义标记 flag 来中断线程
//用flag 标记来中断线程
class MyRunnalbe3 implements Runnable{
public boolean flag = true;
public MyRunnalbe3() {
flag=true;
}
@Override
public void run() {
int i=0;
while(flag) {
System.out.println(Thread.currentThread().getName()+"---"+(i++));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这样,在main方法中可以通过调用该对象更改flag值来中断线程:
mt.flag=false;
这种方式较为简单,比较常
注:以上以上两种中断线程的测试代码如下:
public static void main(String[] args) {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==20) {
//t.interrupt();//作一个中断线程的标记
mt.flag=false;//到此处用标记直接停止
}
}
join的使用
join的作用是当一个线程调用它的join方法时,程序等待该线程执行完毕再继续其他线程。当其他线程在某些执行中需要使用到该线程的执行结果时,可以在该位置使用join方法,以使其他线程执行相关过程时,能够正确得到该线程的执行结果。
public static void main(String[] args) {
MyRunnalbe2 mt = new MyRunnalbe2();
Thread t = new Thread(mt);
t.start();
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==20) {
try {
t.join();//让线程执行完毕,等待t(调用join的线程)执行完毕,main再接着执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
守护线程和用户线程
main线程是一个用户 ,当进程中没有用户线程时,JVM会退出
线程通过调用setDaemon方法来设置,参数为true为设置成守护线程, 参数为false为设置成用户线程
isDaemon方法将测试该线程是否为守护线程
isAlive方法测试线程是否处于活动状态:start以前处于非活动状态,start以后处于活动状态
//该测试通过将t线程设置为守护线程,并且让main线程执行速度快与t线程,可从运行结果中分析出:
//当main线程执行完毕(即进程中没有用户线程时),JVM会退出执行(即便守护进程没有执行完毕)。
public static void main(String[] args) {
MyRunnalbe4 mr4 = new MyRunnalbe4();
Thread t= new Thread(mr4);
//setDaemon将t线程设置为守护线程
t.setDaemon(true);
t.start();
for(int i = 0 ;i<50 ;i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(200);//该线程执行速度较快
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyRunnalbe4 implements Runnable{
@Override
public void run() {
for(int i = 0 ;i<50 ;i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(500);//该线程执行速度较慢
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程安全
多个线程共享数据时,会发生线程不安全的情况。为解决此问题,多个线程必须使用同步。
同步:多个线程在同一时间段内只有一个线程执行指定代码,其他线程要等待此线程执行完成后才可以继续执行。
使用同步代码块实现线程同步
java中同步使用synchronized将要同步的代码放到synchronized(){} 的大括号内,并用一个任意类型的变量充当锁(obj,放到小括号中),每次有线程执行大括号内代码时会将锁关闭,执行完后再将锁打开,锁被关闭时其他线程不能执行这段代码,只有等待。
用于充当锁的变量可以是任意类型。
sleep说明中“sleep不会丢失任何监视器的所有权”的意思是,当该线程处于sleep状态(让出了时间片)时,依然不丢失锁的状态(打开或关闭)。
public static void main(String[] args) {
//开两个线程来卖片……额,卖车票。。。
MyRunnalbe5 mr5 = new MyRunnalbe5();
//两个线程都用mr5赋值
Thread t1 = new Thread(mr5);
Thread t2 = new Thread(mr5);
t1.start();
t2.start();
}
//卖车票的线程
class MyRunnalbe5 implements Runnable{
private int ticket=10;//票的总数
private Object obj = new Object();//用于充当锁的任意类型变量
@Override
public void run() {
for (int i = 0; i < 300; i++) {
if(ticket>0) {
//使用线程同步synchronized
synchronized(obj) {
ticket--;
try {
//sleep不会丢失任何监视器的所有权
//即当该线程处于sleep状态(让出了时间片)时,依然不丢失锁的状态(打开或关闭状态)。
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买后剩余"+ticket+"张票");
}
}
}
}
}
使用同步方法实现线程同步
将同步代码块放到一个用synchronized标识的方法中,在相应位置调用该方法即可。
public static void main(String[] args) {
//开两个线程
MyRunnalbe5 mr5 = new MyRunnalbe5();
//两个线程都用mr5赋值
Thread t1 = new Thread(mr5);
Thread t2 = new Thread(mr5);
t1.start();
t2.start();
}
//卖车票的线程
class MyRunnalbe5 implements Runnable{
private int ticket=10;//票的总数
@Override
public void run() {
for (int i = 0; i < 300; i++) {
method();//直接调用
}
}
//同步方法同步的是当前对象
private synchronized void method() {
if(ticket>0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买后剩余"+ticket+"张票");
}
}
}
使用Lock实现线程同步
Lock是一个接口,通常使用它的实现ReentrantLock来定义锁,ReentrantLock类中有很多方便的操作锁的方法,使操作更灵活。
只需要将上述代码中的method 方法改为如下即可。
//定义一个互斥锁
ReentrantLock lock = new ReentrantLock();
//使用 lock实现同步
private synchronized void method2() {
lock.lock();//上锁
//使用try{}finally{} 可以避免死锁
try {
if(ticket>0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("您购买后剩余"+ticket+"张票");
}
}finally {
lock.unlock();//释放锁
}
}
使用同步能解决安全问题,但是会牺牲性能。所以同步代码块尽量简短,把不随数据变化的代码尽量写在同步代码块之外。
不要阻塞:如InputStream.read(); 如果出现阻塞,会使多个线程都在此等待,都无法继续执行。
在持有锁的时候,不要对其他对象调用同步方法。因为这样容易发生死锁。
sleep 让线程进入休眠,但是不会释放对象锁
wait 让线程进入等待状态,会释放对象锁的所有权,会等待其他线程通过notify唤醒。