线程:存在进程中,一个进程最少得有一个线程(执行路径)。main方法其实就是一个主线程。
线程是一个事物,事物需要用类来描述,那么这个事物又特别常见,
Java工程考虑到这个问题,他就帮我们描述好了这个事物。
创建线程的方式有两种
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法(最好使用这种方式创建线程)
好处:可扩展性更强,可以在实现线程的同时还能与其他类建立子父类关系。
start和run方法的区别:
1.run就是一个普通的方法,被当前线程执行。
2.start 被执行后会创建一个新的线程,然后这个新线程去执行run方法中的代码。
获取当前线程对象可以通过currentThread()方法来完成,
线程不安全的原因:
当多条语句操作线程共享数据,然后某个线程只执行了多条语句的一部分
(CPU执行权就被其他线程拿去了),导致线程共享数据错误。
那么如何解决线程安全问题?
只需要保证多条语句同时只能被一个线程执行,当这个线程执行完成后,
其他线程才能开始执行,就保证共享数据不会出现错误,java就提供有效的解决方案(线程同步)
同步代码块格式:
synchronized(对象)
{
需要同步的代码;
}
同步的作用:保证同步代码块中的代码同一时刻只能被一个线程执行。但是有两个前提:
1.同步需要两个或者两个以上的线程。
2.多个线程使用的是同一个锁。
例:简单的售票程序
class ShouPiao extends Thread
{
private static Object obj=new Object();
private static int count=100;
public void run(){
while(true){
synchronized(obj)
{
count--;
if(count<=0)
break;
try{Thread.sleep(10);}catch(Exception e){};
System.out.println(getName()+":"+count);
}
}
}
}
class Test{
public static void main(String[] args){
ShouPiao th1=new ShouPiao();
th1.start();
ShouPiao th2=new ShouPiao();
th2.start();
ShouPiao th3=new ShouPiao();
th3.start();
ShouPiao th4=new ShouPiao();
th4.start();
}
}
同步函数
与同步代码块原理一样,只是表现方式不同,可以保证同一时刻只能有一个线程执行此方法。
同步函数的锁就是this,静态同步函数的锁是本类字节码对象。
死锁
class My implements Runnable
{
private static boolean flag;
public void run(){
for(int i=0;i<3;i++){
if(flag){
synchronized(MyLock.obj2){
flag=false;
System.out.println("A-锁2");
try{Thread.sleep(1000);}catch(Exception e){};
synchronized(MyLock.obj1){
System.out.println("A-锁1");
}
}
}else{
synchronized(MyLock.obj1){
flag=true;
System.out.println("B-锁1");
try{Thread.sleep(1000);}catch(Exception e){};
synchronized(MyLock.obj2){
System.out.println("B-锁2");
}
}
}
}
}
}
class MyLock{
public static Object obj1=new Object();
public static Object obj2=new Object();
}
class Test
{
public static void main(String[] args){
My my=new My();
Thread th1=new Thread(my);
th1.start();
Thread th2=new Thread(my);
th2.start();
}
}
等待wait()与唤醒notify()
wait()使当前持有锁的线程进入等待状态(注:进入等待状态的线程被唤醒后继续执行后面的程序,不需要执行之前的程序),notify()唤醒通过锁对象进入等待状态的线程(如有多个等待线程,通常先唤醒最先进入等待状态的线程)。
notify()只能唤醒单个程序,唤醒的是自己方的进程则可能导致线程全部进入等待状态,notifyAll()唤醒线程池中所有等待的线程。
停止线程:
改变线程循环标记。
class MyThread extends Thread
{
private boolean flag=true;
public void run(){
while(flag){
System.out.println("run...");
}
}
public void setFlag(boolean flag){
this.flag=flag;
}
}
class Test
{
public static void main(String[] args){
MyThread th=new MyThread();
th.start();
try{Thread.sleep(1000);}catch(Exception e){};
for(int i=0;i<100;i++){
if(i==99){
th.setFlag(false);
}
}
}
}
interrupt中断线程的阻塞状态
在下面代码中,th.interrupt()中断了Thread.sleep(50000)。
将上一段代码run方法改为
public void run(){
while(flag){
try{Thread.sleep(50000);}catch(Exception e){
System.out.println(e.getMessage());
};
System.out.println("run...");
}
}
main方法改为
public static void main(String[] args){
MyThread th=new MyThread();
th.start();
try{Thread.sleep(1000);}catch(Exception e){};
for(int i=0;i<100;i++){
if(i==99){
th.interrupt();//中断阻塞状态
th.setFlag(false);
}
}
}
守护线程:当进程中只存在守护线程后,进程结束。
例:在这段代码for循环执行100次时主线程结束,进程中只存在守护线程,则进程结束。
class MyThread extends Thread
{
public void run(){
while(true){
System.out.println("...."+getName());
}
}
}
class Test
{
public static void main(String[] args){
MyThread th1=new MyThread();
MyThread th2=new MyThread();
th1.setDaemon(true);//更改为守护线程
th2.setDaemon(true);//更改为守护线程
th1.start();
th2.start();
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
join方法
1.这个方法在哪个线程中被调用,就会让哪个线程失去CPU执行权
2.哪个线程对象调用join,那么只有等这个对象的线程结束,执行该调用的线程才有可执行权。
在下面代码中主线程调用join方法,则主线程失去CPU执行权,th1和th2争夺CPU执行权。因为th1调用join方法,因此在th1执行完后主线程才有执行权(若此时th2还未执行完,则主线程和th2争夺CPU执行权)。
class MyThread extends Thread
{
public void run(){
for(int i=0;i<10;i++){
try{Thread.sleep(100);}catch(Exception e){};
System.out.println(getName()+"...."+i);
}
}
}
class Test
{
public static void main(String[] args){
MyThread th1=new MyThread();
MyThread th2=new MyThread();
th1.start();
th2.start();
try{th1.join();}catch(InterruptedException e){
System.out.println(e.getMessage());
};
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}