线程与进程
进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程具有的特征:
·动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
·并发性:任何进程都可以同其他进程一起并发执行;
·独立性:进程是系统进行资源分配和调度的一个独立单位;
·结构性:进程由程序、数据和进程控制块三部分组成。
线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少 有一个线程 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程。
Java的多线程共有五种状态:
·NEW
尚未启动的线程处于此状态。
·RUNNABLE
在Java虚拟机中执行的线程处于此状态。
·BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
·WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。
·TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
·TERMINATED
已退出的线程处于此状态
Java的多线程系统建立于Thread类,它的方法,它的共伴接口Runnable基础上。Thread 类封装了线程的执行。既然你不能直接引用运行着的线程的状态,你要通过它的代理处理 它,于是Thread 实例产生了。为创建一个新的线程,你的程序必须扩展Thread 或实现 Runnable接口。
当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main thread),
因为它是程序开始时就执行的。主线程的重要性体现在两方面:
· 它是产生其他子线程的线程
· 通常它必须最后完成执行,因为它执行各种关闭动作。
Java中主要提供了两种创建线程的方法:
·可以继承Thread类。
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i=0;i<10;i++){
/*
* 利用Thread.currentThread().getName()获取当前线程名
* */
System.out.println(Thread.currentThread().getName()+"主线程运行:"+i);
}
}
}
public class MyThread extends Thread{
/*
* 重写run()方法。
* 新开辟的线程实际是,运行单个任务,而任务则需要写在run()方法中。
* */
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"新线程运行:"+i);
}
}
}
· 实现Runnable 接口。
public class MyRunnable implements Runnable{
/*
* 重写run()方法。
* 新开辟的线程实际是,运行单个任务,而任务则需要写在run()方法中。
* */
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" 新线程运行:"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
/*
* 1.先创建一个任务对象,即实现了Runnable接口的任务类。
* 2.开辟一条新的线程,将任务对象传给线程。
* 3.调用线程的启动。
* */
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" 主线程运行:"+i);
}
}
}
线程安全问题
当多个线程同时启动时,当单个线程抢占时间片进行处理数据的同时,会有其他线程也同时进行处理数据,无法保证数据的安全问题。因此就产生了线程的安全问题。例如以下代码:
public class Demo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
/*
* 启动多个线程
* */
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count = 10;
@Override
public void run() {
while (count>0){
System.out.println("正在卖票!!");
/*
* 利用线程休眠,将问题扩大
* */
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票为"+count);
}
}
}
}
上述代码中,由于三条线程同时执行,当余票为1时,三条线程已经全部运行到循环内部,此时,每天线程都会对count进行减一运算,导致最终结果余票出现负数的情况,导致了多线程的不安全情况。
针对线程不安全的情况,java内部提供了一种保护线程安全的方法,对出现线程不安全的地方进行上锁,利用排队的机制,保护线程的安全。上锁机制主要有三种方法,同步代码块、同步方法,以及显式锁,无论是哪种方法,其实质是当有一个线程在运行代码块时,通过一把锁,将代码锁起来,其余线程只能等第一条线程处理完成后,释放锁后,再进行抢占。同样抢占完成后再次上锁,运行结束后释放锁,由其他线程再次进行抢占。以此往复,保证多线程中的安全问题。同时上锁机制也包含两种不同方式,一种公平锁,即在多个线程抢占式根据先后顺序进行排队,一种不公平锁,所有线程可同时对时间片进行抢占。下面针对三种不同的方式,实现多线程的安全。
方法一:同步代码块(隐式锁)
public class Demo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
/*
* 启动多个线程
* */
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count = 10;
Object o = new Object(); //为所有线程定义同一把锁
@Override
public void run() {
while (true){
synchronized (o){ //利用同步代码块的方法,对其进行上锁
if(count>0){
System.out.println("正在卖票!!");
/*
* 利用线程休眠,将问题扩大
* */
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票为"+count);
}
else {
break;
}
}
}
}
}
}
方法二:同步方法(隐式锁)
public class Demo2 {
public static void main(String[] args) {
Demo1.Ticket ticket = new Demo1.Ticket();
/*
* 启动多个线程
* */
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count = 10;
Object o = new Object(); //为所有线程定义同一把锁
@Override
public void run() {
while (true){
if (sale()){
break;
}
}
}
public synchronized boolean sale(){ //用synchronized关键字修饰方法
if(count>0){
System.out.println("正在卖票!!");
/*
* 利用线程休眠,将问题扩大
* */
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票为"+count);
return true;
}
return false;
}
}
}
方法三:显示锁
public class Demo3 {
public static void main(String[] args) {
Section5.Demo1.Ticket ticket = new Section5.Demo1.Ticket();
/*
* 启动多个线程
* */
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count = 10;
//利用显示锁,对代码块上锁
private Lock lock = new ReentrantLock(true); //此处关键字true即表示锁为公平锁,相反false表示非公平锁
@Override
public void run() {
while (true){
lock.lock(); //加锁
if(count>0){
System.out.println("正在卖票!!");
/*
* 利用线程休眠,将问题扩大
* */
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票为"+count);
}
else {
break;
}
lock.unlock(); //释放锁
}
}
}
}
以上就是Java中的三种解决线程安全的问题,小白一枚,个人理解,希望各位大佬多多指导。