互斥:通过竞争,独占资源,各线程彼此之间不需要知道对方的存在,乱序执行。
同步:协调多个相互关联的线程按一定的顺序,合作完成任务,彼此之间知道对方存在。
创建线程
(1)继承自Thread类
public class MyThread extends Thread{
@Override
public void run() {
//代码
}
}
class Test{
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.run(); //普通的方法调用
myThread.start(); //相当于开启一个新进程,该线程与main线程是平级的
}
}
每新建一个线程,都会在JVM中新建一个栈空间,且每个线程都会争夺进程的CPU。
(2)实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
//获取当前进程的名字
}
}
class TestRunnable{
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable(); //线程的执行体
Thread thread = new Thread(myRunnable, "窗口1");
thread.start(); //启动进程
}
}
优点:
①Runnable更适合处理共享资源。若共享资源定义为static,Thread也能处理。
②避免Java中单继承的局限性。
③实现解耦,代码可以被多个线程共享
(3)Callable接口
class MyCallable implements Callable<Integer>{ //此时泛型要指定具体类型,如Integer、String
@Override
public Integer call() throws Exception {
//代码
return null;
}
}
class TestCallable{
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
MyCallable myCallable = new MyCallable();
Future<Integer> result = pool.submit(myCallable);
pool.shutdown();
Integer value = result.get(); //获取线程的结果
}
}
线程常用方法
(1)设置线程优先级
MyThread myThread = new MyThread();
myThread.setName("线程1");
//为线程设置名字
myThread.setPriority(Thread.NORM_PRIORITY);
//为线程设置优先级
//Java优先级1~10,值越大,优先级越高
//Thread.MAX_PRIORITY = 10
//Thread.MIN_PRIORITY = 1
//Thread.NORM_PRIORITY = 5
优先级高的线程会获得较多的运行机会。
(2)线程休眠
MyThread t = new MyThread();
t.start(); //开启的进程才能休眠
try {
Thread.sleep(1000); //让当前进程休眠n秒,非main线程
} catch (InterruptedException e) {
e.printStackTrace();
}
(3)线程让步
class A extends Thread{
@Override
public void run() {
//代码
}
}
class B extends Thread{
@Override
public void run() {
Thread.yield();
//yield()无法真正达到线程让步的目的
//yield()可以使当前进程放弃CPU的执行权,重新回到可执行状态,但马上又会去竞争CPU的执行权
}
}
class test1{
public static void main(){
new A().start();
new B().start();
}
}
(4)线程合并
主线程的执行会被打断,直到新加入的线程执行完毕,主线程才继续。
class A extends Thread{
@Override
public void run() {
//代码
}
}
class Test{
public static void main(){
A a = new A();
a.start();
try {
a.join(); //线程让步
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
守护线程
当主线程结束时,守护线程也会结束。
class Test{
public static void main(){
MyThread t = new MyThread();
t.setDaemon(true); //设为守护线程
t.start();
}
}
线程的生命周期
(1)新建状态New
当线程对象创建之后进入新建状态。
(2)就绪状态Runnable
当执行start()时,线程进入就绪状态,此时线程随时等待CPU调度执行。
(3)运行状态Running
当CPU开始调度就绪状态的线程时,该线程进入运行状态。
(4)阻塞状态Blocked
运行状态的线程由于某种原因,暂时放弃CPU的使用权,停止运行,此时线程进入阻塞状态。
等待阻塞
运行状态的线程执行wait()。
同步阻塞
线程获取synchronized同步锁失败。此时锁被其他线程占有。
(5)死亡状态Dead
线程执行完毕,或发生异常时,线程进入死亡状态。
线程安全
CPU在高速切换过程中,线程在任何时刻都有可能被抢占CPU的执行权。
原子操作:不可分割的多步操作被视为一个整体。
(1)同步代码块
synchronized,一种同步锁,可以保证多个线程访问共享数据时,同一时刻只会有一个线程操作,其余线程进入阻塞状态。
class SynThread extends Thread{
Object lock = new Object();
//锁对象,任意类型对象都可以充当锁对象
//多个线程都有使用同一个锁,即同一个对象
@Override
public void run() {
synchronized (lock){
//CPU保证只有一个线程能执行这段代码
}
}
}
//多线程可以使用以下方法创建对象
//.class文件加载进内存的时候,只会创建一个该类对象
synchronized(MyThread.class){
//代码
}
(2)同步方法
class SynThread extends Thread{
public synchronized void test(){
//此时,锁对象为this
//synchronized也可以修饰static方法
}
@Override
public void run() {
test();
}
}
(3)Lock
JDK5以后,新加Lock接口,实现类ReentrantLock和synchronized有一样的功能。
class Test{
private Lock lock = new ReentrantLock();
void test(){
lock.lock(); //加锁
try {
//有线程安全问题的代码
}catch (Exception ex){
//错误处理
}finally {
lock.unlock(); //解锁
}
}
}
线程通信——等待唤醒机制
class Test{
public static void main(String[] args) {
Object lock = new Object();
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (lock){ //获得锁
try {
lock.wait(); //释放锁,线程进入等待池
//代码
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程一").start();
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (lock){ //获得锁
lock.notify(); //唤醒一个等待最久的线程。唤醒后的线程进入就绪状态,将重新竞争CPU的执行权
}
}
}, "线程二").start();
}
}
wait(1000*n) //等待n秒
sleep(long m) //在时间内不会释放锁
lock.notifyAll() //唤醒所有等待中的线程
线程池
一个可以容纳多个线程的容器,其中的线程可以反复使用,省去创建和销毁线程的步骤。
顶级接口java.util.concurrent.Executor,是执行线程的工具。
真正的线程池接口java.util.concurrent.Executors。
class Test{
public static void main(String[] args) {
//方式一,创建固定长度的线程池对象,
ExecutorService pool1 = Executors.newFixedThreadPool(3);
MyRunnable myRunnable = new MyRunnable(); //创建一个Runnable接口子类对象
pool1.submit(myRunnable); //提交,提交一次就是启动一个线程
pool1.shutdown(); //关闭线程池
//方式二,创建一个可缓存,大小按需求变化的线程池
ExecutorService pool2 = Executors.newCachedThreadPool();
//方式三,创建一个单线程的线程池,程序按顺序进行
ExecutorService pool3 = Executors.newSingleThreadExecutor();
//方式四,创建一个固定长度的线程池,并以延迟或定时的方式进行
ScheduledExecutorService s = Executors.newScheduledThreadPool(3);
s.schedule(myRunnable, 3, TimeUnit.SECONDS);//启动一个线程,并延迟3秒执行
while(!s.isTerminated()){
//当线程没有执行完毕,main线程将会进入循环
}
s.shutdown(); //关闭线程池,但不会马上终止程序
}
}
线程安全集合
(1)CopyOnWriteArrayList
线程安全的ArrayList。写入时,先copy一个容器副本,再添加新元素,最后替换引用到副本。
(2)CopyOnWriteArraySet
线程安全的ArraySet。
(3)ConcurrentHashMap
线程安全的HashMap。多线程的时候可能出现异常。