多线程学习
线程相关概念
进程:是计算机中程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。简单的理解就是操作系统中正在运行的一个程序。
线程:就是进程的一个执行单元,一个单一顺序的控制流。一个进程至少有一个线程(main函数),进程是线程的容器。每个线程都有自己的线程栈,自己的寄存器环境,自己的线程本地存储。
主线程:JVM启动时会创建一个主线程,该主线程负责执行main方法,主线程就是执行main方法的线程,线程之间存在父子关系,子线程和父线程 属于包含和被包含
串行、并行、并发关系图
并行可以理解为严格更理想的并发
在java中,创建一个线程就是创建一个Thread类(子类)的对象(实例)
Thread类有两个常用的构造方法:Thread()与Thread(Runnable)
定义Thread子类
public class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定义一个Runnable接口的实现类
public class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这俩种创建线程的实质没有区别
主线程调用
public static void main(String[] args) {
ThreadDemo basic = new ThreadDemo();
basic.start();
new Thread(new RunnableDemo()).start();
}
start()方法来启动线程,但是start()的调用顺序并不一定是一定的,是线程调度器来分配执行顺序
线程的设置名称和获取名称 名称的作用主要是为了测试时使用
Thread.currentThread().getName() 获取线程名称
Thread.currentThread().setName("t1") 设置线程名称
isAlive方法测试线程活动状态
boolean alive = basic.isAlive();判断线程是否是激活状态
sleep()方法 让当前线程休眠毫秒数
Thread.sleep(1100);
getId()可以获得线程的唯一标识
long id = basic.getId();
yield()方法的作用是放弃当前CPU资源
basic.yield();
basic.setPriority(5); 设置线程的优先值 1到10
basic.setDaemon(true);守护线程 主线程结束后 子线程也会停止
线程的生命周期是线程对象的生老病死,即线程的状态,线程的生命周期可以通过getState()方法获得,线程的状态是枚举类型定义的 具体的可以查看javaApi Enum Thread.State
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
线程安全问题
非线程安全:主要是指多个线程对同一个对象的实例变量进行操作,会出现值被更改,值不同步的情况。
原子性:就是不可分割的意思,多个线程对共享变量进行操作时,要么已经开始 要么已经结束
访问(读写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,要么尚未发生,即其他线程到当前操作的中间结果,如从ATM机取款
class MyInt{
//java中提供了一个线程安全的AtomicInteger类 ,保证了操作的原子性
AtomicInteger integer = new AtomicInteger();
public int getInt(){
return integer.getAndIncrement();
}
可见性:一个线程对某个共享变量进行更新,后续其他的线程可能无法立即读到这个更新的结果,这就是线程安全问题的另外一种形式。
如果一个线程对共享变量更新后,后续访问该线程的其他线程可以读到更新的结果,称这个线程对共享变量的更新对其他线程可见。
java内存
锁概述
线程安全问题就是多个线程并发访问共享数据
将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问。
锁可以理解为对共享数据进行保护的一个许可证,任何线程想访问这些共享数据必须先持有该许可证。访问后释放其持有的锁(许可证)
锁具有排他性,即一个锁一次只能被一个线程使用。
JVM把锁分为内部锁和显示锁,内部锁通过synchronized关键字实现,显示锁通过java.concurrent.locks.Lock接口来实现
锁的作用
锁可以实现对共享数据的安全访问,保障线程的原子性、可见性和有序性
锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保障临界区的代码一次只能被一个线程执行。
锁的可重入性
如果一个线程持有一个锁的时候还能够继续申请该锁,称该锁是可重入的
void methodA(){
//申请A锁
methodB();
//释放A锁
}
void methodB(){
//申请A锁
.....
//释放A锁
}
内部锁:java中每个对象都有一个与之关联的内部锁(Intrinsic Lock) 这种锁也称为监视器。这种内部锁是一种排它锁,可以保障原子性。
内部锁是通过synchronized关键字实现的
修饰代码块:
synchronized(对象锁){
同步代码块,可以在同步代码块中访问共享数据
}
修饰方法
public void synchronized method(){
代码块
}
public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj.mm()).start();
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
锁对象不同不能实现同步
public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
SynchronizedClass obj1 = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj1.mm()).start();
}
public void mm(){
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
使用常量作为锁对象
public class SynchronizedClass {
public static void main(String[] args) {
SynchronizedClass obj = new SynchronizedClass();
SynchronizedClass obj1 = new SynchronizedClass();
new Thread(()->obj.mm()).start();
new Thread(()->obj1.mm()).start();
}
private static final Object obj = new Object();
public void mm(){
synchronized (obj){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}