Java学习笔记-7
一、基本概念
1.并发:同一时段交替执行
2.并行:同一时段同时执行
3.进程:进入到内存中的程序
4.线程:进程中的执行单元,是应用程序到CPU的执行路径,可以包含多个,
CPU执行时在多个线程之间高速切换,因此多核速度更快
5.主线程:JVM执行main方法时,main方法会进入栈内存,JVM会找操作系统开辟一条main方法通向CPU的路径供其执行,该路径即为主线程。单线程下主线程中执行时出现异常后续代码将不会执行,可以通过采用多线程来规避这一情况的出现
二、多线程
1.创建多线程程序
(1)创建Thread类的子类
1.创建Thread类的子类
2.重写run方法,设置线程任务
3.创建当前类对象
4.调用父类的start方法,开启新线程,执行run方法
先执行优先级高的线程,若优先级相同则随机选择一个执行
(2)使用Runnable接口实现类
1.创建Runnable接口实现类
2.重写run方法,设置线程任务
3.创建当前类对象
4.创建Thread类对象,参数为该接口实现类对象,调用start方法
(3)区别
1.实现Runnable接口可以再继承其它类或实现其它接口,避免了局限性
2.实现Runnable接口把设置线程任务和开启新线程进行分离(解耦)
2.获取线程名
1.Thread类中方法getName()
2.用currentThread()获取当前执行线程对象引用再调用getName()
3.设置线程名
1.Thread类中方法setName()
2.创建带参构造方法,调用父类带参构造方法,让父类给子类线程命名并分配Thread对象
4.暂停线程
sleep(long millis)
当前线程暂停相应毫秒数后继续执行
5.匿名内部类/接口实现类创建线程
package day6;
public class day6 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("A");
//开辟新的栈空间,执行栈方法
//由于在不同栈空间,多线程中的异常并不会相互影响
myThread.start();
new MyThread("XXX").start();
Impl impl = new Impl();
new Thread(impl).start();
//直接调用run还是单线程,仍然在main的栈空间中
// myThread.run();
for(int i = 0; i < 20; i++)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:" + i);
}
//匿名内部类
new Thread()
{
@Override
public void run() {
for(int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName() + " 匿名内部类 " + i);
}
}
}.start();
//匿名实现接口
new Thread(new Runnable()
{
@Override
public void run() {
for(int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName() + " 匿名接口 " + i);
}
}
}).start();
}
}
package day6;
public class MyThread extends Thread {
public MyThread(){}
public MyThread(String name)
{
super(name);
}
@Override
public void run() {
for(int i = 0; i < 20; i++)
{
System.out.println("run:" + i);
}
String name = getName();
System.out.println(name);
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
package day6;
public class Impl implements Runnable{
@Override
public void run() {
for(int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
三、线程安全
1.线程安全问题产生情况
private int ticket = 100;
@Override
public void run() {
while (true)
{
if(ticket > 0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
ticket--;
}
}
}
Runnable run = new RunnableImpl();
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
Thread thread3 = new Thread(run);
thread1.start();
thread2.start();
thread3.start();
三个线程抢夺CPU执行权,在sleep处失去执行权,这时判断条件(ticket数量)会因为其它线程执行而改变,判断条件在不一定成立的情况下向后执行,出现线程安全问题
解决思路:在一个线程涉及到访问共享数据时,不管是否失去执行权其它线程必须等待
2.线程同步
解决访问共享资源时产生的线程安全问题
(1)同步代码块
synchronized
同步使相应区块资源实行互斥访问
synchronized(锁对象)
{
//需要同步操作的代码
}
锁对象可以是任意对象,只需保证每个线程均使用同一对象即可(也就是放在方法外面声明)
锁对象只允许一个线程在同步代码块中执行
while (true)
{
synchronized (obj)
{
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
ticket--;
}
}
}
原理:执行到synchronized代码块时会检查是否有锁对象,如果有则获取并执行,没有就阻塞,执行完归还(这就是为什么锁对象只能有一个)
(2)同步方法
public synchronized void method(){}
把访问共享数据的代码放进去即可
@Override
public void run() {
while (true)
{
payTicket();
}
}
public synchronized void payTicket()
{
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
ticket--;
}
}
原理是一样的,只不过锁对象换成了this(线程实现类对象)
(3)静态同步方法
将方法和相关变量声明为静态也可实现同步,此时锁对象是实现类的class文件对象(如RunnableImpl.class)
(4)Lock锁
//获取锁
void lock();
//释放锁
void unlock();
创建一个ReentrantLock(Lock接口实现类)对象,在可能产生安全问题的代码前后分别调用lock和unlock
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true)
{
lock.lock();
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
ticket--;
}
lock.unlock();
}
}
考虑到总是要释放锁的,为了提高效率,更好的写法是:
while (true)
{
lock.lock();
if(ticket > 0)
{
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
ticket--;
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}