一.进程和线程的区别:
进程:当前计算机正在运行的程序,进程是cpu分配资源的基本单位,一个进程至少有一个线程。
线程: 计算机中任务调度和最小的执行单元,一个线程也被称为轻量级进程。
Java多线程:在单个程序运作的过程中同时运作多个线程,完成不同的工作,称为多线程。
引入线程的好处:Java虚拟机允许应用程序并发的运行多个线程,引入线程可以减少程序并发时的cpu的开销。
二.Java的运行状态图:
初始状态(被创建):创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
运行状态:获得CPU执行权,正在执行的线程。
等待状态:进入等待态的线程会暂时释放CPU执行权,并释放资源。
阻塞状态:处于等待状态的线程,会不断地请求资源,直到请求到资源,才从阻塞状态转换到运行状态。
销亡状态:线程执行结束。
三.如何自定义一个线程?
1.继承一个线程类。
public class ThreadDemo1 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("main...i=" + i);
}
Tred t = new Tred(); //创建一个线程
t.start(); //start()开启线程
}
}
class Tred extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("thread...i=" + i);
}
}
}
2.实现一个接口。
public class ShowTicket {
public static void main(String[] args) {
Tree t=new Tree();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Tree implements Runnable{ //继承一个接口
static int ticket=100;
@Override
public void run() {
while(true) {
try {
Thread.sleep(30);
}catch(Exception e) {}
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+
"正在出售第"+ticket--+"张票");
}
}
}
}
总结:
无论是继承Thread还是实现接口,都要重写run()方法,因为run方法中定义了线程的执行内,而且此方法由JVM自动调用。
顺便说一下Java多线程的执行路径:
程序的执行流程将不会按照原有单线程的执行流程执行,有可能会出现多个线程之间互相打断的情况。
四.synchronized关键字
synchronized:同步
语法:synchronized(对象锁){ 要锁的代码 }
作用:保证线程执行的原子性,也就是说,当前线程执行完毕后,其他线程方可执行。
注意:此处对象锁锁的不是代码块,锁的是对象。当对象发生改变时,同步会失效。
synchronized保证程序原子性的代码实例:
public class ShowTicket1 {
public static void main(String[] args) {
Windows w=new Windows();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable{
private int ticket =100;
Object obj=new Object();
@Override
public void run() {
while(true) {
synchronized(obj) {//原子性 互斥锁,不可再分,代码块必须执行完,保证了代码的完整性。
if(ticket>0) {
//try{TimeUnit.SECONDS.sleep(0);}catch(Exception e){} 睡眠的另一种写法
try{Thread.sleep(50);}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"正在售出第"+ticket--+"张票");
}
}
}
}
}
知识点:
1. 同步和非同步方法能否同时调用?
可以
在同步方法在执行过程中,允许其他线程调用非同步方法。
2.验证同步方法的对象锁是this
思路:开启两个线程,让一个进入同步代码块,另一个进入同步方法,设置一个中间变量boolean flag
实例代码:
public class SynchronizedDemo {
public static void main(String[] args) {
Tt t = new Tt();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(50);
} catch (Exception e) {
}
t.flag=false;
t2.start();
}
}
class Tt implements Runnable {
int count = 10;
Object obj = new Object();
boolean flag=true;
@Override
public void run() {
if (flag) {
while (true) {
synchronized (this) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
if (count > 0)
System.out.println(Thread.currentThread().getName() +
"count=" + count--);
}
}
} else {
while(true)
method();
}
}
public synchronized void method() {
while (true) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
if (count > 0)
System.out.println(Thread.currentThread().getName() +
"count=" + count--);
}
}
3.如果同步代码的对象锁是this,那么这个方法可以写为同步方法。
4.线程死锁:线程死锁:
假设有A,B两个死锁,a线程的执行需要获取b线程的对象锁,b线程的执行需要获取a线程的对象锁
实例代码:
public class DeathLock {
public static void main(String[] args) {
new Thread(new DL(true)).start();
new Thread(new DL(false)).start();
}
}
class Lock {
static final Object LOCKA = new Object();
static final Object LOCKB = new Object();
}
class DL implements Runnable {
boolean flag;
public DL(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (Lock.LOCKA) {
System.out.println("if locka run");
synchronized (Lock.LOCKB) {
System.out.println("if locka run");
}
}
} else {
synchronized (Lock.LOCKB) {
System.out.println("else lockb run");
synchronized (Lock.LOCKA) {
System.out.println("else locka run");
}
}
}
}
}
结果分析:程序出现死锁,彼此都在等待对方释放资源。
5.如果程序运行过程中,出现了异常,则该对象锁会被释放。
实例代码如下:
public class Demo4 {
public static void main(String[] args) {
Demo4 d=new Demo4();
new Thread(()->d.method(),"d1").start();
try {
Thread.sleep(30);
}catch(Exception e) {}
new Thread(()->d.method(),"d2").start();
}
int num=10;
synchronized void method() {
System.out.println(Thread.currentThread().getName()+"start");
while(true) {
num++;
System.out.println(Thread.currentThread().getName()+"num="+ num);
try {
Thread.sleep(30);
}catch(Exception e) {}
if(num==15) {
//try {
int num=1/0;
//}catch(Exception e) {}
}
}
}
}
结果分析:
当发生异常时,当前的对象锁会被释放。所以,在并发过程中出现异常,需要特别小心,否则会出现执行结果不一致的情况
如何解决?
捕获该异常。
6.最好不要用字符串常量作为对象锁。
volatile关键字:可保证线程透明,不具有原子性,但是效率比较高。
可保证线程透明的原因,在此简单的说一下:由于Java的内存模型可知,多个线程之间共享的资源被存放在内存中,并且每个线程都有自己独立的工作区,Java默认每个线程都将获取到内存中的副本,并且拿着这个副本进行操。当有线程对当前变量进行修改时,其他线程将无法感知,所以不会停止运行。所以,使用volatile关键字,会通知所有的线程变量发生了改变,让所有线程都获取到变量的修改值。
注意:volatile不能保证程序运行的原子性,所以,不能代替synchronized。
实例代码如下:
public class Demo6 {
public static void main(String[] args) {
Demo6 d = new Demo6();
List<Thread> list = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
list.add(new Thread(d::method, "list-" + i));
}
list.forEach((o) -> o.start());//让集合中的10个线程都启动
list.forEach((o) -> {
try {
o.join();//让集合中的10个线程先执行完,在执行main线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
System.out.println(d.num);
}
volatile int num = 0;
synchronized void method() {
for (int i = 0; i < 10000; i++) {
num++;
}
}
}
五:
AtomicXXX类本身就是原子性的,且有很多原子性的方法,但是不能保证多个原子性的方法连续使用还是原子性的。
由于++ --是不具有原子性的,所以针对这一问题,显得十分高效。
AtomicXXX类:执行效率比synchronized votalie高
实例代码:
public class Demo8 {
public static void main(String[] args) {
Demo8 d=new Demo8();
List<Thread> list=new ArrayList<Thread> ();
for(int i=0;i<10;i++) {
list.add(new Thread(d::method,"list-"+i));
}
list.forEach((o)->o.start());
list.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
System.out.println(d.num);
}
AtomicInteger num=new AtomicInteger(0);
void method() {
for(int i=0;i<20;i++) {
num.incrementAndGet();//count++
}
}
}