JAVA 线程学习
个人博客文章地址
一、并发与并行
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
并发
个人理解是在同一时间段内,多个程序在人类看起来是同时执行的,但在单核处理器中,其实他们相互交替的运行,而因为他们的交替运行速度非常快(故看起来是在同时运行)如图:
并行
并行这只能在多核处理器中存在,顾名思义,多个程序可以同时执行,大大提高了运行效率,如图:
二、线程与进程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
即:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
三、JAVA线程(Thread)的使用
使用方法一:继承Thread类
目录:
UseThread类:继承Thread类,并且重写run方法
public class UseThread extends Thread{
@Override
public void run() {
System.out.print("我是一个线程");
}
}
ThreadTest类:用作测试,实例化UseThread,并且调用start()方法即可
public class ThreadTest {
public static void main(String[] args) {
Thread test = new UseThread();
test.start();
}
}
使用方法二:实现Runnable接口
UseThread类:实现Runnable接口,并且重写run方法
public class UseThread implements Runnable{
@Override
public void run() {
System.out.print("我是一个线程");
}
}
ThreadTest类:用作测试,实例化UseThread类,新建一个Thread对象,传入UseThread对象,最后调用start()方法即可
public class ThreadTest {
public static void main(String[] args) {
Runnable test = new UseThread();
Thread hello = new Thread(test);
hello.start();
}
}
运行效果:
两种方法的区别大部分是继承和接口的区别,要注意的是实现Runnable的线程能放入线程池中,但是继承Thread的线程不可以所以用实现Runnable接口来使用线程要好一些。
三、多线程使用
其实在上面的例子中已经是一个多线程程序了,main方法已经是一个线程,通过实例化线程对象,并且调用了start()方法就会开启一个新的线程(同时会给该线程分配一个新的栈空间),如果直接调用run方法则会在当前线程运行,我们可以通过Thread.currentThread().getName()方式查看当前运行的线程名字
多线程优点:提高了运行效率,还要挺重要的一点是,各个线程在执行的时候,彼此之间不会产生影响(没有线程通信的情况),即其中一个线程有bug报错,一般情况下其它线程也可以照常运行
main:
示例对象:
结果:
线程安全问题
在JAVA多线程中,各个同优先级的线程是通过互相竞争CPU的使用权来运行的,类似开头的并发操作,各个线程会相互替换在CPU(单核)中运行的,而这种发生就有可能引发线程安全问题,下面用例子说明
UseThread类: 让该线程从100开始递减,直到输出sum 的值是0为止,其中使用Thread.sleep(200)方法是为了增大出现线程安全问题的概率(为了演示),这个后面说明原因
public class UseThread implements Runnable{
private int sum = 100;
@Override
public void run() {
while (true){
if(sum > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread().getName() + "当前sum为" + sum);
sum --;
}
else
break;
}
}
}
ThreadTest测试类: 新建两个线程,但是运行同一个线程对象,用于测试
public class ThreadTest {
public static void main(String[] args) {
Runnable test = new UseThread();
Thread hello = new Thread(test);
Thread world = new Thread(test);
hello.start();
world.start();
}
}
结果出现问题:
相同的sum值
问题出现原因: 线程间相互抢占CPU运行所导致(sleep是为了防止运行速度太快,太快会导致可能看不到这种情况)线程1来不及执行sum- -,就被线程2抢占了CPU运行
四、线程同步
对于出现线程安全问题,我们可以采用线程同步方式
1.同步代码块
synchronized(同步锁(任意类型)){
可能会产生线程安全问题的代码
}
实现: 随便加入一个任意类型的锁
public class UseThread implements Runnable{
private int sum = 100;
String str = "Lock";
@Override
public void run() {
while (true){
synchronized (str){ //同步代码块
if(sum > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread().getName() + "当前sum为" + sum);
sum --;
}
else
break;
}
}
}
}
2.同步方法
public synchronized void method(){
可能会产生线程安全问题的代码
}
实现: 把可能会产生线程安全问题的代码放到同步方法里,然后调用即可
public class UseThread implements Runnable{
private int sum = 100;
//同步方法
public synchronized void method(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread().getName() + "当前sum为" + sum);
sum --;
}
@Override
public void run() {
while (true){
if(sum > 0){
method();
}
else
break;
}
}
}
3. Lock锁
public void lock() :加同步锁。
public void unlock() :释放同步锁。
实现:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class UseThread implements Runnable{
private int sum = 100;
Lock lock = new ReentrantLock();//同步锁
@Override
public void run() {
while (true){
lock.lock();
if(sum > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread().getName() + "当前sum为" + sum);
sum --;
}
else
break;
}
lock.unlock();
}
}
以上三种方法均可实现线程同步。还有线程通信和线程池的使用,学得一般,我就不丢人现眼了~~